3
votes

I am trying to mock a guzzle response from a specific api.

My controller code looks like this (amended for brevity):

class SomeClass
{

    private $guzzle;

    public function __construct(\GuzzleHttp\Client $guzzle) {
        $this->guzzle = new $guzzle();
    }

    public function makeRequest(){

        $client = $this->guzzle;

        $url = 'http//somerurl';
        $options = [];

        $response = $client->request('POST', $url, $options);    

        return $response;
    }
}

And the test looks something like this (again edited)...

public function someTest(){

     $mock = $this->createMock(\GuzzleHttp\Client::class);

     $mock->method('request')->willReturn([
         'response' => 'somedata'
     ]);

     $someClass = new $SomeClass($mock);

     $response = $someClass->makeRequest();

     $body = $response->getBody();

     ...
}

At this point the test returns "Call to a member function getBody on null";

How can the getBody response of a guzzle call be tested?

Thank you in advance...

3

3 Answers

4
votes

One approach to testing with Guzzle is to configure a MockHandler

http://docs.guzzlephp.org/en/stable/testing.html

So instead of mocking the guzzle client, you create one like so:

public function someTest() {

    $mock = new MockHandler([
        new Response(200, [], 'The body!'),
        // Add more responses for each response you need
    ]);

    $handler = HandlerStack::create($mock);
    $client = new Client(['handler' => $handler]);

    $someClass = new SomeClass($client);

    $response = $someClass->makeRequest();

    $body = $response->getBody();

    $this->assertSame('The body!', $body);
}
1
votes

The MockHandler requires you to 'queue' responses, meaning you need to know in what order external API calls will be made. I've taken this a step further and wrapped the MockHandler in another handler capable of stuffing a dummy-response into it at the last moment, if one isn't already waiting in the wings. See https://gist.github.com/kmuenkel/d4d473beb7b2297ac2d8cd480089a738

Just use that trait in your test, and call $this->mockGuzzleResponses(); from the test class's setUp() method. At that point, all requests intended to pass through Guzzle will be available for assertions by way of the $guzzleRequestLog property, and all responses can be mocked by calling $this->guzzleHandler->append(RequestInterface); at the beginning of your test.

Just make sure that all implementations of Guzzle in your code are instantiated by way of the app(Client::class) helper and not new Client. Otherwise the binding override won't take effect. That may have been your issue earlier.

0
votes

Take a look at my composer package https://packagist.org/packages/doppiogancio/mocked-client.

In my opinion, it's a really simple way to mock a Guzzle Client, binding request URLs with responses.

$builder = new HandlerStackBuilder();

// Add a route with a response via callback
$builder->addRoute(
    'GET', '/country/IT', static function (ServerRequestInterface $request): Response {
        return new Response(200, [], '{"id":"+39","code":"IT","name":"Italy"}');
    }
);

// Add a route with a response in a text file
$builder->addRouteWithFile('GET',  '/country/IT/json', __DIR__ . '/fixtures/country.json');

// Add a route with a response in a string
$builder->addRouteWithString('GET',  '/country/IT', '{"id":"+39","code":"IT","name":"Italy"}');

// Add a route mocking directly the response
$builder->addRouteWithResponse('GET', '/admin/dashboard', new Response(401));

$client = new Client(['handler' => $builder->build()]);

Once you did you will have a fully functional client to use normally

$response = $client->request('GET', '/country/DE/json');
$body = (string) $response->getBody();
$country = json_decode($body, true);

print_r($country);

// will return
Array
(
    [id] => +49
    [code] => DE
    [name] => Germany
)