mocking APIs with Guzzle

When working with APIs you sometimes cant use the live API in the tests.
Because you dont own the API, dont want to spam, cant create entities for testing or various other reasons.

Then you need to mock the API and deliever responses from fixtures.
Though there is this term of “Don’t mock what you don’t own” we will mock the API because we dont own it. :)

https://github.com/julienfalque/http-mock is a nice library which helps a lot when mocking an API.
And when your client is using Guzzle there is also a Guzzle handler for HttpMock that makes integration easy.

How does this work?

See below but with caution: its pseudo code but you get the idea dont you? :)

1. lets create a HttpMock Server object through a Factory
and define the endpoints and assign the fixture.

<?php

namespace Nerdpress\Tests;

use GuzzleHttp\Psr7\Response;
use Jfalque\HttpMock\Server;

class HttpMockFactory
{
    public static function create(): Server
    {
        $server = (new Server())->whenUri('http://mock.api/action/ding')
                                ->andWhenMethod('POST')
                                ->return($foo = new Response(200, [],
                                    file_get_contents(__DIR__.'/fixtures/DingResponse.xml')))
                                ->end();

        return $server;
    }
}

2. lets create a mock Guzzle client that takes the HttpMock server and delegates the requests.

<?php

namespace Nerdpress\Tests;

use GuzzleHttp\Client;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Middleware;
use Jfalque\HttpMock\Guzzle\HttpMockHandler;
use Jfalque\HttpMock\Server;
use Psr\Http\Message\RequestInterface;

class HttpClientMock implements ClientInterface
{
    /**
     * @var Server
     */
    protected $server;
    /**
     * @var \GuzzleHttp\Client
     */
    protected $delegate;
    /**
     * The set of requests that have been sent through the client.
     *
     * @var \Psr\Http\Message\RequestInterface[]
     */
    protected $requestHistory = [];

    /**
     * HttpClientMock constructor.
     * @param Server $server
     * @param array $options
     */
    public function __construct(Server $server, array $options = [])
    {
        $this->initialize($server, $options);
    }

    /**
     * {@inheritDoc}
     */
    public function send(RequestInterface $request, array $options = [])
    {
        return $this->delegate->send($request, $options);
    }

    /**
     * {@inheritDoc}
     */
    public function sendAsync(RequestInterface $request, array $options = [])
    {
        return $this->delegate->sendAsync($request, $options);
    }

    /**
     * {@inheritDoc}
     */
    public function request($method, $uri, array $options = [])
    {
        return $this->delegate->request($method, $uri, $options);
    }

    /**
     * {@inheritDoc}
     */
    public function requestAsync($method, $uri, array $options = [])
    {
        return $this->delegate->requestAsync($method, $uri, $options);
    }

    /**
     * {@inheritDoc}
     */
    public function getConfig($option = null)
    {
        return $this->delegate->getConfig($option);
    }

    /**
     * @return RequestInterface[]
     */
    public function getRequestHistory(): array
    {
        return $this->requestHistory;
    }

    /**
     * @param Server $server
     * @param array $options
     */
    private function initialize(Server $server, array $options)
    {
        $this->requestHistory = [];
        $history              = Middleware::history($this->requestHistory);
        $stack                = HttpMockHandler::createStack($server);
        $stack->push($history);
        $client         = new Client(array_merge($options, [
            'handler' => $stack,
        ]));
        $this->delegate = $client;
    }
}

3. lets say we use symfony and create services for the mocks

services:
  Nerdpress\Tests\HttpMockFactory:
  Nerdpress\Tests\HttpClientMock:
    arguments: ["@=service('Nerdpress\\\\Tests\\\\HttpMockFactory').create()", base_uri: 'http://mock.api']

my_bundle:
  client: 'Nerdpress\Tests\HttpClientMock'

4. lets write a test and use the client and work on the returned fixtures

<?php

namespace Nerdpress\Tests;

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class RequestTest extends WebTestCase
{

    protected function setUp()
    {
        parent::setUp();
        static::createClient();
    }

    public function testRequest()
    {
        $container     = static::$kernel->getContainer();

        $request = $container->get('my_bundle.request.with.client');

        $payload = ['testo' => 'bert'];
        $response = $request->send($payload);

        //hey lets have a look at the request
        $reqs = $container->get('Nerdpress\Tests\HttpClientMockt')->getRequestHistory();
        $sendedRequest = $reqs[0]['request']->getBody()->__toString();

        $this->assertEquals($sendedRequest, '<req><action type="testo">bert</action></req>');
        $this->assertEquals('you got pinged', $response->getData());
    }
}

Happy Testing :)