HiveBrain v1.2.0
Get Started
← Back to all entries
patternphpMinor

Testing a Pivotal API request client using lots of mocking

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
pivotalmockingrequestlotsclienttestingusingapi

Problem

I have a class that is all about doing HTTP requests, and logging (in file system & database). It's only using 3 dependencies to do these things, so I'm fine with the code so far.

Here it is for convenience :

```
serviceId = 287;
$this->httpRequest = $httpRequest;
$this->tokenFactory = $tokenFactory;

$this->httpRequest->setHeaders(
[
"Content-Type" => "application/json",
"Authorization" => "Bearer {$this->tokenFactory->getTokenId()}"
]
);

$this->httpRequest->setLogName('pivotal');
$this->requestLog = $requestLog;
}

public function get($uri, $datas = [])
{
return $this->request($uri, $datas, 'get');
}

public function post($uri, $datas = [])
{
return $this->request($uri, $datas, 'post');
}

public function useVersionNumber($versionNumber)
{
$this->checkVersionNumber($versionNumber);

$this->version = "v{$versionNumber}";
}

protected function request($uri, $datas, $method)
{
$this->prepareRequest($uri, $datas);

return $this->getResponse($method);
}

protected function prepareRequest($uri, $datas)
{
$fullUri = $this->getFullUri($uri);

$this->httpRequest->setRequestLog($this->requestLog->fromConnection($this->serviceId, $fullUri));
$this->httpRequest->setUri($fullUri);
$this->httpRequest->setDatas($datas);
}

protected function getResponse($method)
{
if ($method === 'get') {
$response = $this->httpRequest->get();
}
else if ($method === 'post') {
$response = $this->httpRequest->post();
}

return $response;
}

protected function checkVersionNumber($versionNumber)
{
if (!is_int($versionNumber)) {
throw new \InvalidArgumentException('Version number should be a positive int.');
}
}

private function g

Solution

First of all, Bravo for writing unit tests! Secondly, Bravo for doing it in PHP!

Seriously though, I don't see enough people doing this type of testing.

What you're running into is referred to as test pain. Test Pain describes the amount of effort and setup required to sufficiently isolate code dependencies in order to perform unit testing.

In your case, the problem is the HttpRequest class. The class has a deep object graph that your PivotalRequest class knows too many details about.

From here, you have a couple of options:
  1. Test Helpers



One approach here would be to create some helper code in your Unit tests that can easily create default values for mocked HTTP data (like request, cookies, query string, etc...) to help reduce test pain when dealing with HttpRequest.

The reason you have almost ONLY mocks as you said, is because your class is actually pretty compact and simple (which are both good). It is only the sheer size of mocking an HTTP Request compared to the amount of your actual code that makes it look this way.

As far as testing the inner-workings of your class vs. testing what it does. I would suggest renaming your test to clear this up. The convention I usually follow is something like this:

ObjectName_MethodName_GivenArguments_ShouldBehaveAsFollows

Or, for a more concrete example:

PivotalRequest_get_WithValidRequest_ShouldReturnValidResponse

By being that explicit, it becomes clear that you are really only interested in input and output and what happens in-between is less important (unless the test is focused on what is in-between).
  1. Create a lightweight abstraction



If the HttpRequest class is too complicated, it could be that the code smell here is that class does too much and should be split / broken down into smaller chunks.

Also, you could consider creating a wrapper class that is simpler to Mock such as:

class CompactHttpRequest
{
    private $_httpRequest;

    function __construct(HttpRequest $httpRequest)
    {
        $this->_httpRequest = $httpRequest;
    }

    public function setHttpRequestDefaults($queryString, $headers, $formData)
    {
        // TODO: map incoming arguments to $this->_httpRequest
    }
}


Then, use the CompactHttpRequest class in the constructor of your PivotalRequest class instead of HttpRequest. In doing so, you'll only ever need to mock one method. In addition, the only aspect of that mock that you care about is that the setHttpRequestDefaults method is called. The specifics of what is passed becomes irrelevant for the purposes of the test.
  1. Be less specific



In this test, the actual content of the headers being set seems like extra data that isn't necessary to specify if we're not ACTUALLY going to make the HTTP request.
  1. Refactor object relationships



This being my preferred approach as I believe it is more in the spirit of OO programming.

It seems to me like the PivotalRequest doesn't do much as you also note in your question. That being the case, it seems to me to make more sense to have a Class that can process different types of requests and make your PivotalRequest class inherit from the HttpRequest class and override the specifics like the URL being posted to, the HTTP headers, etc...

Here is an illustration I made to demonstrate what I mean:

In this way, your system becomes more flexible and all of your core logic is in one spot in the RequestProcessor class.

Overall, the best way to know what will work for your application and what won't is to decide what type of testing you will do.

I am more of a mockist tester as defined by Martin Fowler, so I am interested in having slightly stronger coupling between my tests and my implementation because it forces me to think more about what I'm doing and although it makes tests a bit more fragile, it also finds more bugs.

For more on this, check out his article here:

http://martinfowler.com/articles/mocksArentStubs.html

Code Snippets

class CompactHttpRequest
{
    private $_httpRequest;

    function __construct(HttpRequest $httpRequest)
    {
        $this->_httpRequest = $httpRequest;
    }

    public function setHttpRequestDefaults($queryString, $headers, $formData)
    {
        // TODO: map incoming arguments to $this->_httpRequest
    }
}

Context

StackExchange Code Review Q#145765, answer score: 3

Revisions (0)

No revisions yet.