@@ -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
13931393This 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
13971412The first way of using ``MockHttpClient `` is to pass a list of responses to its
13981413constructor. 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