Unit Testing API with Guzzle

At the beginning of this post, I’d like to show you how to test APIs by using Laravel or Guzzle. The different is functions provided by Laravel can only be used to test the features built on Laravel. For example, if you have a page for file uploading and you want to test it, it’s very simple by using Laravel.

The unit test way provided by Laravel

Storage::fake('avatars');

response = this->json('POST', '/avatar', [
'avatar' => UploadedFile::fake()->image('avatar.jpg')
]);

// Assert the file was stored...
Storage::disk('avatars')->assertExists('avatar.jpg');

The unit test way provided by Guzzle

But you need another way for testing if you want to test APIs on other platforms. Maybe the APIs provided by Google or Facebook, etc. You can do nothing with these types of APIs unless you construct the code based on Guzzle. Looking into a deeper level of your application and working on the HTTP layer. Mock handler is a very easy and useful tool that will enable you to mock response without sending requests over the internet.

// Create a mock and queue two responses.
$mock = new MockHandler([
new Response(200, ['X-Foo' => 'Bar']),
new Response(202, ['Content-Length' => 0]),
new RequestException("Error Communicating with Server", new Request('GET', 'test'))
]);

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

// The first request is intercepted with the first response.
echo client->request('GET', '/')->getStatusCode();
//> 200
// The second request is intercepted with the second response.
echo client->request('GET', '/')->getStatusCode();
//> 202

The unit test way using in our projects

A media management platform was migrated into our project. The 3rd part API has a version identifier and are working steadily so we won't make changes to the SDK. For the quality purpose, the SDK codes should be covered by unit tests. The big problem is there are so many functions and each request of a function is not quite rapid. We must find out a way to run test case without sending requests in the deployment and we also don't want to mock response for every request manually. When you look into the Guzzle, you could find the Guzzle has the ability to dump HTTP data. We can dump the data to files first and import it in the test step as a functionality verification. This's the way how we test APIs in one of our projects.

Step 1: Add a middleware dump HTTP data in the initialization.

if (\App::environment() == 'debug') {
logger = new Logger('Debug');
logger->pushHandler(new ApiDataDumpHandler(Logger::DEBUG));
stack = HandlerStack::create();
stack->push(Middleware::log($logger, new ApiLogFormatter()));
}

The data dumping code should be put in the ApiDataDumpHandler.php. The log middleware provided by Guzzle to record logs is GuzzleHttp\Middleware::log().

Steps 2: Create a mockHandle to import the HTTP plain data and return it as a response data.

if (\App::environment() == 'testing') {
mockedResponse = function (RequestInterface request) {
simulator = new ResponseSimulator();
return simulator->mockResponse(request);
};
mock = new MockHandler([mockedResponse,]);
stack = HandlerStack::create(mock);
} else {
stack = HandlerStack::create();
}

In this testing flow, I'm using the hash value of URI as UUID to map the API requests and dumped data.

After we started to test API in this way, we saved 99% of the time in running cases for API and do not need to worry about some unexpected issues when running tests in the processing of deployment, such as the 3rd part platform is not accessible.