Unit Testing Guzzle clients

Guzzle provides several tools that will enable you to easily unit test your web service clients.

  • PHPUnit integration
  • Mock responses
  • node.js web server for integration testing

PHPUnit integration

Guzzle is unit tested using PHPUnit. Your web service client's unit tests should extend Guzzle\Tests\GuzzleTestCase so that you can take advantage of some of the built in helpers.

In order to unit test your client, a developer would need to copy phpunit.xml.dist to phpunit.xml and make any needed modifications. As a best practice and security measure for you and your contributors, it is recommended to add an ignore statement to your SCM so that phpunit.xml is ignored.

Bootstrapping

Your web service client should have a tests/ folder that contains a bootstrap.php file. The bootstrap.php file responsible for autoloading and configuring a Guzzle\Service\Builder\ServiceBuilder that is used throughout your unit tests for loading a configured client. You can add custom parameters to your phpunit.xml file that expects users to provide the path to their configuration data.

Guzzle\Tests\GuzzleTestCase::setServiceBuilder(Aws\Common\Aws::factory($_SERVER['CONFIG']));

Guzzle\Tests\GuzzleTestCase::setServiceBuilder(Guzzle\Service\Builder\ServiceBuilder::factory(array(
    'test.unfuddle' => array(
        'class' => 'Guzzle.Unfuddle.UnfuddleClient',
        'params' => array(
            'username' => 'test_user',
            'password' => '****',
            'subdomain' => 'test'
        )
    )
)));

The above code registers a service builder that can be used throughout your unit tests. You would then be able to retrieve an instantiated and configured Unfuddle client by calling $this->getServiceBuilder()->get('test.unfuddle). The above code assumes that $_SERVER['CONFIG'] contains the path to a file that stores service description configuration.

Unit testing remote APIs

Mock responses

One of the benefits of unit testing is the ability to quickly determine if there are errors in your code. If your unit tests run slowly, then they become tedious and will likely be run less frequently. Guzzle's philosophy on unit testing web service clients is that no network access should be required to run the unit tests. This means that responses are served from mock responses or local servers. By adhering to this principle, tests will run much faster and will not require an external resource to be available. The problem with this approach is that your mock responses must first be gathered and then subsequently updated each time the remote API changes.

Integration testing over the internet

You can perform integration testing with a web service over the internet by making calls directly to the service. If the web service you are requesting uses a complex signing algorithm or some other specific implementation, then you may want to include at least one actual network test that can be run specifically through the command line using PHPUnit group annotations.

@group internet annotation

When creating tests that require an internet connection, it is recommended that you add @group internet annotations to your unit tests to specify which tests require network connectivity.

You can then run PHPUnit tests that exclude the @internet group by running phpunit --exclude-group internet.

API credentials

If API credentials are required to run your integration tests, you must add <php> parameters to your phpunit.xml.dist file and extract these parameters in your bootstrap.php file.

<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="./tests/bootstrap.php" colors="true">
    <php>
        <!-- Specify the path to a service configuration file -->
        <server name="CONFIG" value="test_services.json" />
        <!-- Or, specify each require parameter individually -->
        <server name="API_USER" value="change_me" />
        <server name="API_PASSWORD" value="****" />
    </php>
    <testsuites>
        <testsuite name="guzzle-service">
            <directory suffix="Test.php">./Tests</directory>
        </testsuite>
    </testsuites>
</phpunit>

You can then extract the server variables in your bootstrap.php file by grabbing them from the $_SERVER superglobal: $apiUser = $_SERVER['API_USER'];

Further reading

A good discussion on the topic of testing remote APIs can be found in Sebastian Bergmann's Real-World Solutions for Developing High-Quality PHP Frameworks and Applications.

Queueing Mock responses

Mock responses can be used to test if requests are being generated correctly and responses and handled correctly by your client. Mock responses can be queued up for a client using the $this->setMockResponse($client, $path) method of your test class. Pass the client you are adding mock responses to and a single path or array of paths to mock response files relative to the /tests/mock/ folder. This will queue one or more mock responses for your client by creating a simple observer on the client. Mock response files must contain a full HTTP response message:

HTTP/1.1 200 OK
Date: Wed, 25 Nov 2009 12:00:00 GMT
Connection: close
Server: AmazonS3
Content-Type: application/xml

<?xml version="1.0" encoding="UTF-8"?>
<LocationConstraint xmlns="http://s3.amazonaws.com/doc/2006-03-01/">EU</LocationConstraint>

After queuing mock responses for a client, you can get an array of the requests that were sent by the client that were issued a mock response by calling $this->getMockedRequests().

You can also use the Guzzle\Plugin\Mock\MockPlugin object directly with your clients.

$plugin = new Guzzle\Plugin\Mock\MockPlugin();
$plugin->addResponse(new Guzzle\Http\Message\Response(200));
$client = new Guzzle\Http\Client();
$client->addSubscriber($plugin);

// The following request will get the mock response from the plugin in FIFO order
$request = $client->get('http://www.test.com/');
$request->send();

// The MockPlugin maintains a list of requests that were mocked
$this->assertContainsOnly($request, $plugin->getReceivedRequests());

node.js web server for integration testing

Using mock responses is usually enough when testing a web service client. If your client needs to add custom cURL options to requests, then you should use the node.js test web server to ensure that your HTTP request message is being created correctly.

Guzzle is based around PHP's libcurl bindings. cURL sometimes modifies an HTTP request message based on CURLOPT_* options. Headers that are added to your request by cURL will not be accounted for if you inject mock responses into your tests. Additionally, some request entity bodies cannot be loaded by the client before transmitting it to the sever (for example, when using a client as a sort of proxy and streaming content from a remote server). You might also need to inspect the entity body of a multipart/form-data POST request.

Note

You can skip all of the tests that require the node.js test web server by excluding the server group: phpunit --exclude-group server

Using the test server

The node.js test server receives requests and returns queued responses. The test server exposes a simple API that is used to enqueue responses and inspect the requests that it has received.

Retrieve the server object by calling $this->getServer(). If the node.js server is not running, it will be started as a forked process and an object that interfaces with the server will be returned. (note: stopping the server is handled internally by Guzzle.)

You can queue an HTTP response or an array of responses by calling $this->getServer()->enqueue():

$this->getServer()->enqueue("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");

The above code queues a single 200 response with an empty body. Responses are queued using a FIFO order; this response will be returned by the server when it receives the first request and then removed from the queue. If a request is received by a server with no queued responses, an exception will be thrown in your unit test.

You can inspect the requests that the server has retrieved by calling $this->getServer()->getReceivedRequests(). This method accepts an optional $hydrate parameter that specifies if you are retrieving an array of string HTTP requests or an array of Guzzle\Http\RequestInterface subclassed objects. "Hydrating" the requests will allow greater flexibility in your unit tests so that you can easily assert the state of the various parts of a request.

You will need to modify the base_url of your web service client in order to use it against the test server.

$client = $this->getServiceBuilder()->get('my_client');
$client->setBaseUrl($this->getServer()->getUrl());

After running the above code, all calls made from the $client object will be sent to the test web server.

comments powered by Disqus