Unit Testing with GuzzlePHP

Tweet

In January, Miguel Romero wrote a great article showing how to get started with Guzzle. If you missed the article, or are unfamiliar with Guzzle:

Guzzle is a PHP HTTP client & framework for building RESTful web service clients.

In Miguel’s article, he covered a range of the library’s features, including basic configuration for GET and POST requests, plugins, such as logging using monolog, and finished up with interacting with an OAuth service, specifically GitHub’s API.

In this tutorial, I want to show you how to use Guzzle from a different perspective, specifically how to do unit testing with it. To do this, we’re going to look at three specific approaches:

  1. Hand Crafting Custom Responses
  2. Using a ServiceClient with Mock Response Files
  3. Enqueueing a Server with Mock Responses

Getting Set Up

Note: The source code for this article is available on Github.

Like all things, we need to walk before we can run. In this case, we need to set up our test environment and test class. To get everything in place, we’ll be using Composer. If you’re not familiar with Composer, please read Alexander’s article here on SitePoint before continuing.

Our composer.json file will look as follows:

{
    "require": {
        "php": ">=5.3.3",
    }
    "require-dev": {
        "phpunit/phpunit": "4.0.*",
        "guzzle/guzzle": "~3.7"
    }
}

I’ve stipulated a minimum PHP version of 5.3.3. To be fair, it likely should be higher, but this is a good start. Our only other requirements are PHPUnit and Guzzle. After adding these to composer.json, in your project run composer install and after a short wait, the dependencies will be ready to go.

Preparing PHPUnit

Before we can run our unit tests, we need to do a bit of preparation there as well. First, create a directory in your project called tests. In there, create two files: bootstrap.php and phpunit.xml.dist.

bootstrap.php is quite simple:

<?php
error_reporting(E_ALL | E_STRICT);
require dirname(__DIR__) . '/vendor/autoload.php';

This includes the auto-generated autoload.php file from the vendor directory, which Composer created. It ensures we have access to both PHPUnit and Guzzle. Next, let’s look at phpunit.xml.dist.

<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="./bootstrap.php" colors="true">
    <testsuites>
        <testsuite name="importer-tests">
            <directory suffix="Test.php">./</directory>
        </testsuite>
    </testsuites>
</phpunit>

This is a rather rudimentary configuration. What it does is:

  • Tell PHPUnit to use bootstrap.php to bootstrap the test environment
  • Use colors in the test output (handy for discerning the import aspects)
  • Set up one testsuite, called tests. This looks for tests in all files that end in Test.php, located anywhere under the current directory

There is quite an array of available options and configurations, but this suits our needs. If you’re interested, check out the configuration documentation for more details.

With that done, let’s start building our test class. In the tests directory, create a new file called SitePointGuzzleTest.php. In it, add the following:

<?php
use Guzzle\Tests\GuzzleTestCase,
    Guzzle\Plugin\Mock\MockPlugin,
    Guzzle\Http\Message\Response,
    Guzzle\Http\Client as HttpClient,
    Guzzle\Service\Client as ServiceClient,
    Guzzle\Http\EntityBody;

class SitePointGuzzleTest extends GuzzleTestCase 
{
    protected $_client;
}

Here, we’ve imported the key classes which our test class needs. Our class extends from GuzzleTestCase, giving it access to some of the Guzzle-specific test functionality we’ll be using. So far, so good. Let’s look at custom responses.

Hand Crafting Custom Responses

From Miguel’s article, you’ll be familiar with initializing a client to make a request, potentially passing in parameters, then inspecting the response. Let’s assume you’ve created a class which either uses or wraps a Guzzle client, or uses a Guzzle Response object.

And let’s say you’re working with the FreeAgent API and you want to test code which retrieves invoice data.

You want to be sure that your code reacts as required, should something go wrong or the API change. Let’s look at how to mock a response, by going through some annotated code.

public function testAnotherRequest() {
    $mockResponse = new Response(200);
    $mockResponseBody = EntityBody::factory(fopen(
        './mock/bodies/body1.txt', 'r+')
    );
    $mockResponse->setBody($mockResponseBody);
    // ...
}

Here we first instantiate a new Response object. We then use the factory method of Guzzle\Http\EntityBody to set the response body with the contents of the file ./mock/bodies/body1.txt.

This both makes it easy to separate configuration from code and makes test maintenance simpler. body1.txt is available in the source code for this article.

$mockResponse->setHeaders(array(
    "Host" => "httpbin.org",
    "User-Agent" => "curl/7.19.7 (universal-apple-darwin10.0) libcurl/7.19.7 OpenSSL/0.9.8l zlib/1.2.3",
    "Accept" => "application/json",
    "Content-Type" => "application/json"
));

We then pass an associative array to setHeaders, which will set four custom headers on our response.

$plugin = new MockPlugin();
$plugin->addResponse($mockResponse);
$client = new HttpClient();
$client->addSubscriber($plugin);

Here we create a new MockPlugin object, passing in the response object to it. Once we instantiate our new HttpClient, we add the MockPlugin as a subscriber, which in turn will ensure requests made with it use the mock response we’ve just created.

$request = $client->get(
    'https://api.freeagent.com/v2/invoices'
);
$response = $request->send();

Now, as in Miguel’s article, we call get on the client, and send on the returned request to get a response object. With that, we can run a series of test assertions.

$this->assertEquals(200, $response->getStatusCode());
$this->assertTrue(in_array(
    'Host', array_keys($response->getHeaders()->toArray())
));
$this->assertTrue($response->hasHeader("User-Agent"));
$this->assertCount(4, $response->getHeaders());
$this->assertSame(
    $mockResponseBody->getSize(), 
    $response->getBody()->getSize()
);
$this->assertSame(
    1, 
    count(json_decode($response->getBody(true))->invoices
));

In the assertions, you can see that I’ve checked the response code, if Host and User-Agent were in the response headers, the number of headers sent, that the size of the body matched the size of our faked response body and that there was one invoice in the response received.

This is just a small sample of the kinds of tests which can be run, but it shows just how easy it is to both mock a custom response and to test it when it’s retrieved. What other kinds of tests would you run?

Using a ServiceClient with Mock Response Files

That was the long way to mock up a response. If you remember, I emphasised at the start that our test class extends GuzzleTestCase, to get access to some excellent testing functionality. Let’s look at how we can skip a lot of the work we just did by using it.

This time, let’s override setUp as follows:

public function setUp()
{
    $this->_client = new ServiceClient();
    $this->setMockBasePath('./mock/responses');
    $this->setMockResponse(
        $this->_client, array('response1')
    );
}

Here, we’ve instantiated the class variable $_client as a new ServiceClient. We’ve then used setMockBasePath, to ./mock/responses, and called setMockResponse, passing in our client object and an array.

The array lists the names of files in ‘./mock/responses’. The contents of these files will be used to set up a series of responses, which the client will receive for each successive call to send.

In this case, I’ve only added one, but you could easily add as many as you like. You can see in ./mock/responses/response1, that it lists the HTTP version, status code, headers and response body. Once again, it keeps the code and configuration neatly separate.

Now, let’s look at the function which uses it.

public function testRequests() {
    $request = $this->_client->get(
        'https://api.freeagent.com/v2/invoices'
    );
    $request->getQuery()->set(
        'view', 'recent_open_or_overdue'
    );
    $response = $request->send();

    $this->assertContainsOnly(
        $request, $this->getMockedRequests()
    );
    $this->assertEquals(200, $response->getStatusCode());
    $this->assertEquals(
        'AmazonS3', $response->getServer()
    );
    $this->assertEquals(
        'application/xml', $response->getContentType()
    );
}

You can see that all I’ve had to do is to make a call to get on the client object and send on the returned request object, as we normally would. I’ve added in query parameters just for good measure.

As before, I’ve then been able to run a series of assertions, checking the mocked requests, returned status code, server and content-type header.

One thing worth mentioning is that it’s a FIFO queue; meaning that the first response added is the first which will be sent. Don’t let this trip you up.

Enqueueing a Server with Mock Responses

Finally, let’s look at one of the cooler aspects of Guzzle. If we call $this->getServer()->enqueue(array());, Guzzle transparently starts a node.js server behind the scenes. We can then use that to send requests to, as if it was our real server endpoint. Let’s have a look at how to use it.

public function testWithRemoteServer() {
    $mockProperties = array(
        array(
            'header' => './mock/headers/header1.txt',
            'body' => './mock/bodies/body1.txt',
            'status' => 200
        )
    );
}

Here I create an array to store header, body, and status information for a mock request, specifying a status code of 200, and files containing the header and body response data.

$mockResponses = array();

foreach($mockProperties as $property) {
    $mockResponse = new Response($property['status']);
    $mockResponseBody = EntityBody::factory(
        fopen($property['body'], 'r+')
    );
    $mockResponse->setBody($mockResponseBody);
    $headers = explode(
        "\n", 
        file_get_contents($property['header'], true)
    );
    foreach($headers as $header) {
        list($key, $value) = explode(': ', $header);
        $mockResponse->addHeader($key, $value);
    }
    $mockResponses[] = $mockResponse;
}

Then I’ve created a new Response object, setting the status code, and again used the factory method of the EntityBody class to set the body. The headers were a bit more cumbersome, so I’ve iterated over the contents of the file, calling addHeader for each key/value pair retrieved.

$this->getServer()->enqueue($mockResponses);

Each mock response object created is added to an array, which is then passed to enqueue. Now it has a set of responses ready to be sent to our client requests.

$client = new HttpClient();
$client->setBaseUrl($this->getServer()->getUrl());
$request = $client->get();
$request->getQuery()->set(
    'view', 'recent_open_or_overdue'
);
$response = $request->send();

Here, as before, we’ve initialized our client object, calling get and send on the returned request. The one thing to note, is that to use the Node.JS server, we need to pass $this->getServer()->getUrl() to $client->setBaseUrl(), otherwise it won’t work.

$this->assertCount(5, $response->getHeaders());
$this->assertEmpty($response->getContentDisposition());
$this->assertSame('HTTP', $response->getProtocol());

Following that, our code works as it did before, and I’ve added assertions for the headers, content disposition header and the protocol in the returned response object.

Wrapping Up

So, what do you think? Though it does take a bit of work, I’m sure you can see how simple it is to set up the different response approaches. I’m sure with a little work, you could also simplify the approaches I’ve taken.

I know that this only scratched the surface of what’s possible with testing in Guzzle, but I hope I piqued your curiosity, showing you a wealth of ways to ensure that your client code is fully test covered.

What’s your experience? Share your thoughts in the comments.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • MarcioAlmada

    Shouldn’t guzzle and phpunit be on require-dev?

    • http://www.matthewsetter.com/ Matthew Setter

      Hi @MarcioAlmada:disqus, Guzzle, I’d say in this instance yes. But potentially not necessarily. phpunit, definitely should be. I’ll get that sorted out. Thanks for mentioning it.

      • http://www.matthewsetter.com/ Matthew Setter

        @MarcioAlmada:disqus done. All updated.

        • MarcioAlmada

          Nice. Great article BTW.

          • http://www.matthewsetter.com/ Matthew Setter

            Thanks. Happy to do what I can. The article was motivated by an experience I had integrating with different vendors. I found Guzzle when it was clearly apparent I needed a way to simply, quickly and most of all easily verify that the code worked and if unannounced changes were made. Guzzle was a godsend in that regard.

        • MarcioAlmada

          Nice. Great article BTW.

  • Stephen Gulick

    Great article. I can’t seem to locate the mentioned code. Want to see what body1.txt looks like. Thanks