Skip to content

Commit a149ca8

Browse files
committed
minor #15385 [HttpClient] More on testing, how to test request, example (SirRFI)
This PR was squashed before being merged into the 4.4 branch. Discussion ---------- [HttpClient] More on testing, how to test request, example PR focused on HTTP Client's testing. Specifically: * Adds more to general description * Adds subsection about testing request data (such as which method was used, what headers/body, etc.) + example * Adds standalone PHPUnit-based example - demonstrates how to replace client for testing and how to do assertions Commits ------- 2c27ceb [HttpClient] More on testing, how to test request, example
2 parents 9ba6e27 + 2c27ceb commit a149ca8

File tree

1 file changed

+134
-4
lines changed

1 file changed

+134
-4
lines changed

http_client.rst

Lines changed: 134 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1387,12 +1387,27 @@ This allows using them where native PHP streams are needed::
13871387
// later on if you need to, you can access the response from the stream
13881388
$response = stream_get_meta_data($streamResource)['wrapper_data']->getResponse();
13891389

1390-
Testing HTTP Clients and Responses
1391-
----------------------------------
1390+
Testing
1391+
-------
13921392

13931393
This component includes the ``MockHttpClient`` and ``MockResponse`` classes to
1394-
use them in tests that need an HTTP client which doesn't make actual HTTP
1395-
requests.
1394+
use in tests that shouldn't make actual HTTP requests. Such tests can be
1395+
useful, as they will run faster and produce consistent results, since they're
1396+
not dependant on an external service. By not making actual HTTP requests there
1397+
is no need to worry about the service being online or the request changing
1398+
state, for example deleting a resource.
1399+
1400+
``MockHttpClient`` implements the ``HttpClientInterface``, just like any actual
1401+
HTTP client in this component. When you type-hint with ``HttpClientInterface``
1402+
your code will accept the real client outside tests, while replacing it with
1403+
``MockHttpClient`` in the test.
1404+
1405+
When the ``request`` method is used on ``MockHttpClient``, it will respond with
1406+
the supplied ``MockResponse``. There are a few ways to use it, as described
1407+
below.
1408+
1409+
HTTP Client and Responses
1410+
~~~~~~~~~~~~~~~~~~~~~~~~~
13961411

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

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

1469+
Testing Request Data
1470+
~~~~~~~~~~~~~~~~~~~~
1471+
1472+
The examples above describe how to return desired response. What if you wanted
1473+
to also test the request itself? ``MockResponse`` comes with a few helper
1474+
methods:
1475+
1476+
* ``getRequestMethod()`` - returns the HTTP method
1477+
* ``getRequestUrl()`` - returns the URL the request would be sent to
1478+
* ``getRequestOptions()`` - returns an array containing other information about
1479+
the request such as headers, query parameters, body content etc.
1480+
1481+
Usage example::
1482+
1483+
$mockResponse = new MockResponse('', ['http_code' => 204]);
1484+
$httpClient = new MockHttpClient($mockResponse, 'https://example.com');
1485+
1486+
$response = $httpClient->request('DELETE', 'api/article/1337', [
1487+
'headers' => [
1488+
'Accept: */*',
1489+
'Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l',
1490+
],
1491+
]);
1492+
1493+
// returns "DELETE"
1494+
$mockResponse->getRequestMethod();
1495+
1496+
// returns "https://example.com/api/article/1337"
1497+
$mockResponse->getRequestUrl();
1498+
1499+
// returns ["Accept: */*", "Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l"]
1500+
$mockResponse->getRequestOptions()['headers'];
1501+
1502+
Example
1503+
~~~~~~~
1504+
1505+
The following standalone example demonstrates a way to use HTTP client and
1506+
test it in a real application::
1507+
1508+
// ExternalArticleService.php
1509+
use Symfony\Contracts\HttpClient\HttpClientInterface;
1510+
1511+
final class ExternalArticleService
1512+
{
1513+
private HttpClientInterface $httpClient;
1514+
1515+
public function __construct(HttpClientInterface $httpClient)
1516+
{
1517+
$this->httpClient = $httpClient;
1518+
}
1519+
1520+
public function createArticle(array $requestData): array
1521+
{
1522+
$requestJson = json_encode($requestData, JSON_THROW_ON_ERROR);
1523+
1524+
$response = $this->httpClient->request('POST', 'api/article', [
1525+
'headers' => [
1526+
'Content-Type: application/json',
1527+
'Accept: application/json',
1528+
],
1529+
'body' => $requestJson,
1530+
]);
1531+
1532+
if (201 !== $response->getStatusCode()) {
1533+
throw new Exception('Response status code is different than expected.');
1534+
}
1535+
1536+
// ... other checks
1537+
1538+
$responseJson = $response->getContent();
1539+
$responseData = json_decode($responseJson, true, 512, JSON_THROW_ON_ERROR);
1540+
1541+
return $responseData;
1542+
}
1543+
}
1544+
1545+
// ExternalArticleServiceTest.php
1546+
use PHPUnit\Framework\TestCase;
1547+
use Symfony\Component\HttpClient\MockHttpClient;
1548+
use Symfony\Component\HttpClient\Response\MockResponse;
1549+
1550+
final class ExternalArticleServiceTest extends TestCase
1551+
{
1552+
public function testSubmitData(): void
1553+
{
1554+
// Arrange
1555+
$requestData = ['title' => 'Testing with Symfony HTTP Client'];
1556+
$expectedRequestData = json_encode($requestData, JSON_THROW_ON_ERROR);
1557+
1558+
$expectedResponseData = ['id' => 12345];
1559+
$mockResponseJson = json_encode($expectedResponseData, JSON_THROW_ON_ERROR);
1560+
$mockResponse = new MockResponse($mockResponseJson, [
1561+
'http_code' => 201,
1562+
'response_headers' => ['Content-Type: application/json'],
1563+
]);
1564+
1565+
$httpClient = new MockHttpClient($mockResponse, 'https://example.com');
1566+
$service = new ExternalArticleService($httpClient);
1567+
1568+
// Act
1569+
$responseData = $service->createArticle($requestData);
1570+
1571+
// Assert
1572+
self::assertSame('POST', $mockResponse->getRequestMethod());
1573+
self::assertSame('https://example.com/api/article', $mockResponse->getRequestUrl());
1574+
self::assertContains(
1575+
'Content-Type: application/json',
1576+
$mockResponse->getRequestOptions()['headers']
1577+
);
1578+
self::assertSame($expectedRequestData, $mockResponse->getRequestOptions()['body']);
1579+
1580+
self::assertSame($responseData, $expectedResponseData);
1581+
}
1582+
}
1583+
14541584
.. _`cURL PHP extension`: https://www.php.net/curl
14551585
.. _`PSR-17`: https://www.php-fig.org/psr/psr-17/
14561586
.. _`PSR-18`: https://www.php-fig.org/psr/psr-18/

0 commit comments

Comments
 (0)