Guzzle, it is a popular PHP HTTP client that makes sending HTTP requests to web services a pretty easy task.

When testing api requests, it is often desirable to not actually hit an api. The exception, of course, is when you are running an integration tests that is specifically designed to hit the actual api. Fortunately, Guzzle 6 make this task very easy through the use of their Mock Handler.

The TL;DR Version:

  1. Create a new MockHandler instance and add one or more response objects or request exceptions to the queue
  2. Create the handler stack by passing the mock handler as an argument to the handler stacks static create method.
  3. Create a new Guzzle Client instance and pass the handler stack in the constructor.

A Bit More Explanation

In this example I have an api client that makes a request to an external service. The api client object depends on Guzzle as the HTTP request handler. As I mentioned above, I am not interested in testing the actual communication to the service in the bulk of the test suite since I will do that separately in a dedicated integration test suite. Using the Guzzle MockHandler, I was able to predefine the json responses and http status codes that the application expects.

For each request that is executed on that Guzzle client instance, we’ll get back the next response object or request exception we defined in the mock handler. When the stack is exhausted, an OutOfBoundsException is thrown.

The nice thing here that we don’t have to modify our existing code. Just define the new client and register it in the IoC container to use the client with the mocked handler in favor of the client in your codebase.

To make this easier to use across test classes, all of the logic is extracted to a trait.

trait FakeGuzzleClientResponses
{
	public $mockHandler;

	public function setupTestGuzzleClient()
	{
		$this->mockHandler = new MockHandler;
		$handler = HandlerStack::create($this->mockHandler);
		$client = new Client(['handler' => $handler]);
		
		$this->app->instance(GuzzleHttp\Client::class, $client);
	}

	public function appendToHandler($statusCode = 200, $headers = [], $body = '', $version = '1.1', $reason = null)
	{
		if (! $this->mockHandler) {
			$this->setupTestGuzzleClient();
		}
	  
		$this->mockHandler->append(new Response($statusCode, $headers, $body, $version, $reason));
	}
	...
}

The setupTestGuzzleClient method takes care of instantiating the mock handler adding it to the stack and passing the stack to the GuzzleHttp\Client. Then we tell the container to give us the instance of our client with the mocked handler rather than the one we are using in production.

Some of the tests may require more than one call to the api so rather than instantiating a mock handler and a new GuzzleHttp\Client each time, we check if we already have an existing handler and if we do, we just append a new request to it with the appendToHandler method.

These two methods are all we need and will work nicely if you import the trait and call the appendToHandler method with the necessary arguments to build the response.

In my case all of my response bodies contain json data. It feels a little cleaner to me to put the expected response in it’s own text file rather than passing it to the apppendToHandler method from my test. I also find that it is easier to read and change if the api ever changes. Here is a sample of an error response body I am expecting to get back.

{
  "error": {
    "status": 403,
    "title": "Request Authentication Failure",
    "code": "12345",
    "detail": "The client ID provided is invalid"
  }
}

Since I might want to use this particular request in multiple tests, I extract the process of creating the response to its own method on my trait.

public function fakeGuzzleInvalidClientIdResponse()
{
	$expectedResponseBody = file_get_contents('tests/sampleResponses/invalidClientId.json');
	$this->appendToHandler(403, [], $expectedResponseBody);
}

All this does is read in the json string from the sample response file and sets an http status code through the appendToHandler method parameters. The second parameter is an array of headers but I’ve left that out intentionally for this example.

All that is left to is import the trait in my test suite and make a single call to the response I am expecting in my test.

use FakeGuzzleClientResponses;

/** @test */
function the_request_fails_if_the_client_id_is_invalid()
{
	// Arrange
	$this->fakeGuzzleInvalidClientIdResponse();

	// Act

	// Assert
}

One line of code and my GuzzleHttp\Client will be using my fake data rather than needlessly hitting the real api. I also have the ability to create custom requests anywhere else that it is needed.