Testing Guzzle Clients

Guzzle provides several tools that will enable you to easily mock the HTTP layer without needing to send requests over the internet.

  • Mock subscriber
  • Mock handler
  • Node.js web server for integration testing

Mock Subscriber

When testing HTTP clients, you often need to simulate specific scenarios like returning a successful response, returning an error, or returning specific responses in a certain order. Because unit tests need to be predictable, easy to bootstrap, and fast, hitting an actual remote API is a test smell.

Guzzle provides a mock subscriber that can be attached to clients or requests that allows you to queue up a list of responses to use rather than hitting a remote API.

  1. use GuzzleHttp\Client;
  2. use GuzzleHttp\Subscriber\Mock;
  3. use GuzzleHttp\Message\Response;
  4. $client = new Client();
  5. // Create a mock subscriber and queue two responses.
  6. $mock = new Mock([
  7. new Response(200, ['X-Foo' => 'Bar']), // Use response object
  8. "HTTP/1.1 202 OK\r\nContent-Length: 0\r\n\r\n" // Use a response string
  9. ]);
  10. // Add the mock subscriber to the client.
  11. $client->getEmitter()->attach($mock);
  12. // The first request is intercepted with the first response.
  13. echo $client->get('/')->getStatusCode();
  14. //> 200
  15. // The second request is intercepted with the second response.
  16. echo $client->get('/')->getStatusCode();
  17. //> 202

When no more responses are in the queue and a request is sent, an OutOfBoundsException is thrown.

History Subscriber

When using things like the Mock subscriber, you often need to know if the requests you expected to send were sent exactly as you intended. While the mock subscriber responds with mocked responses, the GuzzleHttp\Subscriber\History subscriber maintains a history of the requests that were sent by a client.

  1. use GuzzleHttp\Client;
  2. use GuzzleHttp\Subscriber\History;
  3. $client = new Client();
  4. $history = new History();
  5. // Add the history subscriber to the client.
  6. $client->getEmitter()->attach($history);
  7. $client->get('http://httpbin.org/get');
  8. $client->head('http://httpbin.org/get');
  9. // Count the number of transactions
  10. echo count($history);
  11. //> 2
  12. // Get the last request
  13. $lastRequest = $history->getLastRequest();
  14. // Get the last response
  15. $lastResponse = $history->getLastResponse();
  16. // Iterate over the transactions that were sent
  17. foreach ($history as $transaction) {
  18. echo $transaction['request']->getMethod();
  19. //> GET, HEAD
  20. echo $transaction['response']->getStatusCode();
  21. //> 200, 200
  22. }

The history subscriber can also be printed, revealing the requests and responses that were sent as a string, in order.

  1. echo $history;
  1. > GET /get HTTP/1.1
  2. Host: httpbin.org
  3. User-Agent: Guzzle/4.0-dev curl/7.21.4 PHP/5.5.8
  4. < HTTP/1.1 200 OK
  5. Access-Control-Allow-Origin: *
  6. Content-Type: application/json
  7. Date: Tue, 25 Mar 2014 03:53:27 GMT
  8. Server: gunicorn/0.17.4
  9. Content-Length: 270
  10. Connection: keep-alive
  11. {
  12. "headers": {
  13. "Connection": "close",
  14. "X-Request-Id": "3d0f7d5c-c937-4394-8248-2b8e03fcccdb",
  15. "User-Agent": "Guzzle/4.0-dev curl/7.21.4 PHP/5.5.8",
  16. "Host": "httpbin.org"
  17. },
  18. "origin": "76.104.247.1",
  19. "args": {},
  20. "url": "http://httpbin.org/get"
  21. }
  22. > HEAD /get HTTP/1.1
  23. Host: httpbin.org
  24. User-Agent: Guzzle/4.0-dev curl/7.21.4 PHP/5.5.8
  25. < HTTP/1.1 200 OK
  26. Access-Control-Allow-Origin: *
  27. Content-length: 270
  28. Content-Type: application/json
  29. Date: Tue, 25 Mar 2014 03:53:27 GMT
  30. Server: gunicorn/0.17.4
  31. Connection: keep-alive

Mock Adapter

In addition to using the Mock subscriber, you can use the GuzzleHttp\Ring\Client\MockHandler as the handler of a client to return the same response over and over or return the result of a callable function.

Test Web Server

Using mock responses is almost always enough when testing a web service client. When implementing custom HTTP handlers, you’ll need to send actual HTTP requests in order to sufficiently test the handler. However, a best practice is to contact a local web server rather than a server over the internet.

  • Tests are more reliable
  • Tests do not require a network connection
  • Tests have no external dependencies

Using the test server

Warning

The following functionality is provided to help developers of Guzzle develop HTTP handlers. There is no promise of backwards compatibility when it comes to the node.js test server or the GuzzleHttp\Tests\Server class. If you are using the test server or Server class outside of guzzlehttp/guzzle, then you will need to configure autoloading and ensure the web server is started manually.

Hint

You almost never need to use this test web server. You should only ever consider using it when developing HTTP handlers. The test web server is not necessary for mocking requests. For that, please use the Mock subcribers and History subscriber.

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

Any operation on the Server object will ensure that the server is running and wait until it is able to receive requests before returning.

  1. use GuzzleHttp\Client;
  2. use GuzzleHttp\Tests\Server;
  3. // Start the server and queue a response
  4. Server::enqueue("HTTP/1.1 200 OK\r\n\Content-Length: 0r\n\r\n");
  5. $client = new Client(['base_url' => Server::$url]);
  6. echo $client->get('/foo')->getStatusCode();
  7. // 200

GuzzleHttp\Tests\Server provides a static interface to the test server. You can queue an HTTP response or an array of responses by calling Server::enqueue(). This method accepts a string representing an HTTP response message, a GuzzleHttp\Message\ResponseInterface, or an array of HTTP message strings / GuzzleHttp\Message\ResponseInterface objects.

  1. // Queue single response
  2. Server::enqueue("HTTP/1.1 200 OK\r\n\Content-Length: 0r\n\r\n");
  3. // Clear the queue and queue an array of responses
  4. Server::enqueue([
  5. "HTTP/1.1 200 OK\r\n\Content-Length: 0r\n\r\n",
  6. "HTTP/1.1 404 Not Found\r\n\Content-Length: 0r\n\r\n"
  7. ]);

When a response is queued on the test server, the test server will remove any previously queued responses. As the server receives requests, queued responses are dequeued and returned to the request. When the queue is empty, the server will return a 500 response.

You can inspect the requests that the server has retrieved by calling Server::received(). This method accepts an optional $hydrate parameter that specifies if you are retrieving an array of HTTP requests as strings or an array of GuzzleHttp\Message\RequestInterface objects.

  1. foreach (Server::received() as $response) {
  2. echo $response;
  3. }

You can clear the list of received requests from the web server using the Server::flush() method.

  1. Server::flush();
  2. echo count(Server::received());
  3. // 0