Back to Guzzle

Testing Guzzle Clients

docs/testing-guzzle-clients.md

8.0.06.4 KB
Original Source

Testing Guzzle Clients

This page covers Guzzle's local testing tools for client code and handler development. Most tests should mock the HTTP layer without sending requests over the internet.

  • Mock handler
  • History middleware
  • Node.js web server for integration testing

Choosing a Test Tool

Use MockHandler when your test needs predictable responses, errors, or response ordering. Combine it with history middleware when your test also needs to assert the request method, URI, headers, body, or request options that your code sent.

Use the separate guzzlehttp/test-server package only when you need a local HTTP server, usually while developing or testing a custom handler. Unit tests and most application client tests should not use the test server.

Avoid remote services in automated tests unless the test is an explicit, opt-in integration test for that service.

Mock Handler

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 handler that can be used to fulfill HTTP requests with queued responses, response promises, request-aware callables, or reject them with queued throwables by shifting return values off of a queue.

php
use GuzzleHttp\Client;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Response;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Exception\RequestException;

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

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

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

// Reset the queue and queue up a new response
$mock->reset();
$mock->append(new Response(201));

// As the mock was reset, the new response is the 201 CREATED,
// instead of the previously queued RequestException
echo $client->request('GET', '/')->getStatusCode();
//> 201

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

Queued callables receive the Psr\Http\Message\RequestInterface and request options array passed to the mock handler. They may return a Psr\Http\Message\ResponseInterface, a GuzzleHttp\Promise\PromiseInterface<Psr\Http\Message\ResponseInterface, mixed>, or a throwable rejection reason.

The on_headers request option is invoked when a queued response or fulfilled response promise is available. It is not invoked for queued throwables or rejected promises.

The optional MockHandler constructor callbacks are invoked with the fulfilled response or rejected reason after the queued value has settled.

History Middleware

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

php
use GuzzleHttp\Client;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use GuzzleHttp\Psr7\Response;

$container = [];
$history = Middleware::history($container);
$mock = new MockHandler([
    new Response(200, ['Content-Type' => 'application/json'], '{"ok":true}'),
    new Response(204),
]);

$handlerStack = HandlerStack::create($mock);

// Add the history middleware to the handler stack.
$handlerStack->push($history);

$client = new Client([
    'base_uri' => 'https://example.test',
    'handler' => $handlerStack,
]);

$client->request('GET', '/users/123');
$client->request('DELETE', '/users/123');

// Count the number of transactions
echo count($container);
//> 2

// Iterate over the transaction history. Each transaction contains:
// - request: the request that was sent
// - response: the response, or null when the transfer was rejected
// - error: the rejection reason, or null when the transfer succeeded
// - options: the request options used for the transfer
foreach ($container as $transaction) {
    echo $transaction['request']->getMethod();
    //> GET, DELETE
    if ($transaction['response']) {
        echo $transaction['response']->getStatusCode();
        //> 200, 204
    } elseif ($transaction['error']) {
        echo $transaction['error'];
        //> exception
    }
    var_dump($transaction['options']);
    //> dumps the request options of the sent request.
}

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

[!TIP] 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 handler and history middleware.

The test server is distributed separately as guzzlehttp/test-server. It is not installed with Guzzle by default. The package provides a Node.js server that receives requests, returns responses from a queue, and records received requests for inspection.

See the Test Server Usage documentation for lifecycle, response queuing, request inspection, and shutdown details.