| 1 |
====================== |
| 2 |
Testing Guzzle Clients |
| 3 |
====================== |
| 4 |
|
| 5 |
Guzzle provides several tools that will enable you to easily mock the HTTP |
| 6 |
layer without needing to send requests over the internet. |
| 7 |
|
| 8 |
* Mock handler |
| 9 |
* History middleware |
| 10 |
* Node.js web server for integration testing |
| 11 |
|
| 12 |
|
| 13 |
Mock Handler |
| 14 |
============ |
| 15 |
|
| 16 |
When testing HTTP clients, you often need to simulate specific scenarios like |
| 17 |
returning a successful response, returning an error, or returning specific |
| 18 |
responses in a certain order. Because unit tests need to be predictable, easy |
| 19 |
to bootstrap, and fast, hitting an actual remote API is a test smell. |
| 20 |
|
| 21 |
Guzzle provides a mock handler that can be used to fulfill HTTP requests with |
| 22 |
a response or exception by shifting return values off of a queue. |
| 23 |
|
| 24 |
.. code-block:: php |
| 25 |
|
| 26 |
use GuzzleHttp\Client; |
| 27 |
use GuzzleHttp\Handler\MockHandler; |
| 28 |
use GuzzleHttp\HandlerStack; |
| 29 |
use GuzzleHttp\Psr7\Response; |
| 30 |
use GuzzleHttp\Psr7\Request; |
| 31 |
use GuzzleHttp\Exception\RequestException; |
| 32 |
|
| 33 |
// Create a mock and queue two responses. |
| 34 |
$mock = new MockHandler([ |
| 35 |
new Response(200, ['X-Foo' => 'Bar'], 'Hello, World'), |
| 36 |
new Response(202, ['Content-Length' => 0]), |
| 37 |
new RequestException('Error Communicating with Server', new Request('GET', 'test')) |
| 38 |
]); |
| 39 |
|
| 40 |
$handlerStack = HandlerStack::create($mock); |
| 41 |
$client = new Client(['handler' => $handlerStack]); |
| 42 |
|
| 43 |
// The first request is intercepted with the first response. |
| 44 |
$response = $client->request('GET', '/'); |
| 45 |
echo $response->getStatusCode(); |
| 46 |
//> 200 |
| 47 |
echo $response->getBody(); |
| 48 |
//> Hello, World |
| 49 |
// The second request is intercepted with the second response. |
| 50 |
echo $client->request('GET', '/')->getStatusCode(); |
| 51 |
//> 202 |
| 52 |
|
| 53 |
// Reset the queue and queue up a new response |
| 54 |
$mock->reset(); |
| 55 |
$mock->append(new Response(201)); |
| 56 |
|
| 57 |
// As the mock was reset, the new response is the 201 CREATED, |
| 58 |
// instead of the previously queued RequestException |
| 59 |
echo $client->request('GET', '/')->getStatusCode(); |
| 60 |
//> 201 |
| 61 |
|
| 62 |
|
| 63 |
When no more responses are in the queue and a request is sent, an |
| 64 |
``OutOfBoundsException`` is thrown. |
| 65 |
|
| 66 |
History Middleware |
| 67 |
================== |
| 68 |
|
| 69 |
When using things like the ``Mock`` handler, you often need to know if the |
| 70 |
requests you expected to send were sent exactly as you intended. While the mock |
| 71 |
handler responds with mocked responses, the history middleware maintains a |
| 72 |
history of the requests that were sent by a client. |
| 73 |
|
| 74 |
.. code-block:: php |
| 75 |
|
| 76 |
use GuzzleHttp\Client; |
| 77 |
use GuzzleHttp\HandlerStack; |
| 78 |
use GuzzleHttp\Middleware; |
| 79 |
|
| 80 |
$container = []; |
| 81 |
$history = Middleware::history($container); |
| 82 |
|
| 83 |
$handlerStack = HandlerStack::create(); |
| 84 |
// or $handlerStack = HandlerStack::create($mock); if using the Mock handler. |
| 85 |
|
| 86 |
// Add the history middleware to the handler stack. |
| 87 |
$handlerStack->push($history); |
| 88 |
|
| 89 |
$client = new Client(['handler' => $handlerStack]); |
| 90 |
|
| 91 |
$client->request('GET', 'http://httpbin.org/get'); |
| 92 |
$client->request('HEAD', 'http://httpbin.org/get'); |
| 93 |
|
| 94 |
// Count the number of transactions |
| 95 |
echo count($container); |
| 96 |
//> 2 |
| 97 |
|
| 98 |
// Iterate over the requests and responses |
| 99 |
foreach ($container as $transaction) { |
| 100 |
echo $transaction['request']->getMethod(); |
| 101 |
//> GET, HEAD |
| 102 |
if ($transaction['response']) { |
| 103 |
echo $transaction['response']->getStatusCode(); |
| 104 |
//> 200, 200 |
| 105 |
} elseif ($transaction['error']) { |
| 106 |
echo $transaction['error']; |
| 107 |
//> exception |
| 108 |
} |
| 109 |
var_dump($transaction['options']); |
| 110 |
//> dumps the request options of the sent request. |
| 111 |
} |
| 112 |
|
| 113 |
|
| 114 |
Test Web Server |
| 115 |
=============== |
| 116 |
|
| 117 |
Using mock responses is almost always enough when testing a web service client. |
| 118 |
When implementing custom :doc:`HTTP handlers <handlers-and-middleware>`, you'll |
| 119 |
need to send actual HTTP requests in order to sufficiently test the handler. |
| 120 |
However, a best practice is to contact a local web server rather than a server |
| 121 |
over the internet. |
| 122 |
|
| 123 |
- Tests are more reliable |
| 124 |
- Tests do not require a network connection |
| 125 |
- Tests have no external dependencies |
| 126 |
|
| 127 |
|
| 128 |
Using the test server |
| 129 |
--------------------- |
| 130 |
|
| 131 |
.. warning:: |
| 132 |
|
| 133 |
The following functionality is provided to help developers of Guzzle |
| 134 |
develop HTTP handlers. There is no promise of backwards compatibility |
| 135 |
when it comes to the node.js test server or the ``GuzzleHttp\Tests\Server`` |
| 136 |
class. If you are using the test server or ``Server`` class outside of |
| 137 |
guzzlehttp/guzzle, then you will need to configure autoloading and |
| 138 |
ensure the web server is started manually. |
| 139 |
|
| 140 |
.. hint:: |
| 141 |
|
| 142 |
You almost never need to use this test web server. You should only ever |
| 143 |
consider using it when developing HTTP handlers. The test web server |
| 144 |
is not necessary for mocking requests. For that, please use the |
| 145 |
Mock handler and history middleware. |
| 146 |
|
| 147 |
Guzzle ships with a node.js test server that receives requests and returns |
| 148 |
responses from a queue. The test server exposes a simple API that is used to |
| 149 |
enqueue responses and inspect the requests that it has received. |
| 150 |
|
| 151 |
Any operation on the ``Server`` object will ensure that |
| 152 |
the server is running and wait until it is able to receive requests before |
| 153 |
returning. |
| 154 |
|
| 155 |
``GuzzleHttp\Tests\Server`` provides a static interface to the test server. You |
| 156 |
can queue an HTTP response or an array of responses by calling |
| 157 |
``Server::enqueue()``. This method accepts an array of |
| 158 |
``Psr\Http\Message\ResponseInterface`` and ``Exception`` objects. |
| 159 |
|
| 160 |
.. code-block:: php |
| 161 |
|
| 162 |
use GuzzleHttp\Client; |
| 163 |
use GuzzleHttp\Psr7\Response; |
| 164 |
use GuzzleHttp\Tests\Server; |
| 165 |
|
| 166 |
// Start the server and queue a response |
| 167 |
Server::enqueue([ |
| 168 |
new Response(200, ['Content-Length' => 0]) |
| 169 |
]); |
| 170 |
|
| 171 |
$client = new Client(['base_uri' => Server::$url]); |
| 172 |
echo $client->request('GET', '/foo')->getStatusCode(); |
| 173 |
// 200 |
| 174 |
|
| 175 |
When a response is queued on the test server, the test server will remove any |
| 176 |
previously queued responses. As the server receives requests, queued responses |
| 177 |
are dequeued and returned to the request. When the queue is empty, the server |
| 178 |
will return a 500 response. |
| 179 |
|
| 180 |
You can inspect the requests that the server has retrieved by calling |
| 181 |
``Server::received()``. |
| 182 |
|
| 183 |
.. code-block:: php |
| 184 |
|
| 185 |
foreach (Server::received() as $response) { |
| 186 |
echo $response->getStatusCode(); |
| 187 |
} |
| 188 |
|
| 189 |
You can clear the list of received requests from the web server using the |
| 190 |
``Server::flush()`` method. |
| 191 |
|
| 192 |
.. code-block:: php |
| 193 |
|
| 194 |
Server::flush(); |
| 195 |
echo count(Server::received()); |
| 196 |
// 0 |
| 197 |
|