Calamity Jane
Calamity Jane

Reputation: 2686

symfony: How to mock a response to an Guzzle Api request depending on the requested url

I am working on a symfony 2 project. I have a class which depending on the input values creates an url and starts an request to an external system. The response is processed and the result of the processing is given back.

For this class the functional tests are working. In that case it means I am running tests, which really call the external service and process the real answer. Now I want to add real unit tests, so I have to mock my request and the result. For the request I am using the Guzzle Http Client. What I want to achieve is, that if my method is calling url "http://example.com/certain/parameters" only then I expect the answer "baa". By this I want to test that the request url is built correctly and the resulting response is processed correctly.

However I am completely stuck how to do the mocking.

The test runs a public method which calls a private one. Here is the part of my class' private method, which contains the Guzzle:

/**
 * Fetch the Preview Urls from Cache Server.
 *
 * @param string  $sCacheUrl
 * @param integer $iObjnbr
 *
 * @return mixed
 */
private function fetchPreviewUrls($sCacheUrl, $iObjnbr)
{
    $this->client = $this->client->get($sCacheUrl);
    $response = $this->client->send();
    if ($response->getStatusCode() <> 200) {
        $this->logger->error('Image cache server unreachable. Http status "' . $response->getStatusCode() . '"');
    } else {
        $this->logger->debug('new cache url is "' . $response->getBody(true) . '"');
    }
    $json = $response->getBody(true);
    //echo $json;

    $aPreviews = $this->processJson($json, $iObjnbr);
    //var_dump($aPreviews);
    $aTeaser = array_shift($aPreviews);
    if (empty($aTeaser)) {
        $aTeaser = $aPreviews;
    }
    //var_dump($aTeaser);
    return $aTeaser['url'];
}

The tricky part is that the url is set in the "get" method of $client. Then the "send" method in used to get the response, which is a object returned by the send method. And I want to connect the input value of the get call with the result of the send call.

I tried around a lot but nothing really worked so far.

One of the non-working examples being:

public function testGetPreviewUrlBigImage()
{

    $this->mockLogger = $this->getMock('\Psr\Log\LoggerInterface');
    // given
    $url = 'http://i5.example.com/teaser.php?action=list&objnbr=60963198&size=320';
    $json =
        '{"60963198":{"0":{"url":"http:\/\/i1.example.com\/teaser\/320\/8\/60963198.jpeg","size":320,"type":""}}}';

    $clientMethods = get_class_methods('\Guzzle\Http\Client');
    $this->mockClient = $this->getMock('\Guzzle\Http\Client', $clientMethods);
    $this->mockClient->expects($this->any())->method('get')->method('send')->will(
        $this->returnCallback(
            function ($argument)  use ($json, $url) {
                $response = new \Guzzle\Http\Message\Response(200, [], $json);
                return ($argument == $url) ? $response : false;
            }
        )
    );
    $this->linkService = new DefaultPreviewImageLinkService($this->mockLogger, $this->mockClient);
    // when
    $previewUrl = $this->linkService->getPreviewUrl(60963198, '', DefaultPreviewImageLinkService::IMAGE_BIG);

    // then
    $this->assertStringEndsWith('.jpeg', $previewUrl);
    $this->assertRegExp('/^http:\/\/i[0-9]{1}.*/', $previewUrl);
    $this->assertRegExp('/.*320.*jpeg/', $previewUrl);
}

Which results in a Fatal error PHP Fatal error: Call to a member function getPreviewUrl() on null

Any one has a hint how to achieve this? Is it even possible?

Upvotes: 2

Views: 5031

Answers (1)

Calamity Jane
Calamity Jane

Reputation: 2686

With the help of francisco-spaeth I got this solved:

This is how we did it:

three private properties were added to the test class:

private $mockLogger;
private $mockClient;
private $linkService;

I added a method for preparing the mocks

public function prepareMocks($url, $json)
{
    $responseInterface = $this->getMockBuilder('\Guzzle\Http\Message\Response')
                              ->disableOriginalConstructor()
                              ->getMock();
    $responseInterface->method('getBody')
                      ->with($this->equalTo(true))
                      ->will($this->returnValue($json));

    $requestInterface = $this->getMock('\Guzzle\Http\Message\RequestInterface');
    $requestInterface->method('send')->will($this->returnValue($responseInterface));

    $this->mockClient = $this->getMock('\Guzzle\Http\Client');

    $this->mockClient->method('get')
                     ->with($this->equalTo($url))
                     ->will($this->returnValue($requestInterface));
    $this->linkService = new DefaultPreviewImageLinkService($this->mockLogger, $this->mockClient);
}

In the test methods it is called like follows:

public function testGetPreviewUrlBigImage()
{
    // Given:
    $url = 'http://i1.example.com/teaser.php?action=list&objnbr=60963198&size=320';
    $json = '{"60963198":{"0":{"url":"http:\/\/i1.example.com\/teaser\/320\/8\/60963198.jpeg",'
        . '"size":320,"type":""}}}';
    $this->prepareMocks($url, $json);

    $class_methods = get_class_methods($this->linkService);
    // When:
    $previewUrl = $this->linkService->getPreviewUrl(60963198, '', DefaultPreviewImageLinkService::IMAGE_BIG);
    // Then:
    $this->assert.....
}

By preparing the mocks in a method I keep the code clean, because this has to be called for each test. I just feed the desired input valued into the prepareMock method.

By this the mock behaves like it should: it only gives back the desired value if the matching value is used by my tested class.

Upvotes: 1

Related Questions