Skip to content

[HttpClient] More on testing, how to test request, example #15385

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 6, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 134 additions & 4 deletions http_client.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1387,12 +1387,27 @@ This allows using them where native PHP streams are needed::
// later on if you need to, you can access the response from the stream
$response = stream_get_meta_data($streamResource)['wrapper_data']->getResponse();

Testing HTTP Clients and Responses
----------------------------------
Testing
-------

This component includes the ``MockHttpClient`` and ``MockResponse`` classes to
use them in tests that need an HTTP client which doesn't make actual HTTP
requests.
use in tests that shouldn't make actual HTTP requests. Such tests can be
useful, as they will run faster and produce consistent results, since they're
not dependant on an external service. By not making actual HTTP requests there
is no need to worry about the service being online or the request changing
state, for example deleting a resource.

``MockHttpClient`` implements the ``HttpClientInterface``, just like any actual
HTTP client in this component. When you type-hint with ``HttpClientInterface``
your code will accept the real client outside tests, while replacing it with
``MockHttpClient`` in the test.

When the ``request`` method is used on ``MockHttpClient``, it will respond with
the supplied ``MockResponse``. There are a few ways to use it, as described
below.

HTTP Client and Responses
~~~~~~~~~~~~~~~~~~~~~~~~~

The first way of using ``MockHttpClient`` is to pass a list of responses to its
constructor. These will be yielded in order when requests are made::
Expand Down Expand Up @@ -1451,6 +1466,121 @@ However, using ``MockResponse`` allows simulating chunked responses and timeouts

$mockResponse = new MockResponse($body());

Testing Request Data
~~~~~~~~~~~~~~~~~~~~

The examples above describe how to return desired response. What if you wanted
to also test the request itself? ``MockResponse`` comes with a few helper
methods:

* ``getRequestMethod()`` - returns the HTTP method
* ``getRequestUrl()`` - returns the URL the request would be sent to
* ``getRequestOptions()`` - returns an array containing other information about
the request such as headers, query parameters, body content etc.

Usage example::

$mockResponse = new MockResponse('', ['http_code' => 204]);
$httpClient = new MockHttpClient($mockResponse, 'https://example.com');

$response = $httpClient->request('DELETE', 'api/article/1337', [
'headers' => [
'Accept: */*',
'Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l',
],
]);

// returns "DELETE"
$mockResponse->getRequestMethod();

// returns "https://example.com/api/article/1337"
$mockResponse->getRequestUrl();

// returns ["Accept: */*", "Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l"]
$mockResponse->getRequestOptions()['headers'];

Example
~~~~~~~

The following standalone example demonstrates a way to use HTTP client and
test it in a real application::

// ExternalArticleService.php
use Symfony\Contracts\HttpClient\HttpClientInterface;

final class ExternalArticleService
{
private HttpClientInterface $httpClient;

public function __construct(HttpClientInterface $httpClient)
{
$this->httpClient = $httpClient;
}

public function createArticle(array $requestData): array
{
$requestJson = json_encode($requestData, JSON_THROW_ON_ERROR);

$response = $this->httpClient->request('POST', 'api/article', [
'headers' => [
'Content-Type: application/json',
'Accept: application/json',
],
'body' => $requestJson,
]);

if (201 !== $response->getStatusCode()) {
throw new Exception('Response status code is different than expected.');
}

// ... other checks

$responseJson = $response->getContent();
$responseData = json_decode($responseJson, true, 512, JSON_THROW_ON_ERROR);

return $responseData;
}
}

// ExternalArticleServiceTest.php
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpClient\MockHttpClient;
use Symfony\Component\HttpClient\Response\MockResponse;

final class ExternalArticleServiceTest extends TestCase
{
public function testSubmitData(): void
{
// Arrange
$requestData = ['title' => 'Testing with Symfony HTTP Client'];
$expectedRequestData = json_encode($requestData, JSON_THROW_ON_ERROR);

$expectedResponseData = ['id' => 12345];
$mockResponseJson = json_encode($expectedResponseData, JSON_THROW_ON_ERROR);
$mockResponse = new MockResponse($mockResponseJson, [
'http_code' => 201,
'response_headers' => ['Content-Type: application/json'],
]);

$httpClient = new MockHttpClient($mockResponse, 'https://example.com');
$service = new ExternalArticleService($httpClient);

// Act
$responseData = $service->createArticle($requestData);

// Assert
self::assertSame('POST', $mockResponse->getRequestMethod());
self::assertSame('https://example.com/api/article', $mockResponse->getRequestUrl());
self::assertContains(
'Content-Type: application/json',
$mockResponse->getRequestOptions()['headers']
);
self::assertSame($expectedRequestData, $mockResponse->getRequestOptions()['body']);

self::assertSame($responseData, $expectedResponseData);
}
}

.. _`cURL PHP extension`: https://www.php.net/curl
.. _`PSR-17`: https://www.php-fig.org/psr/psr-17/
.. _`PSR-18`: https://www.php-fig.org/psr/psr-18/
Expand Down