8000 feature #34871 [HttpClient] Allow pass array of callable to the mocki… · symfony/symfony@00b6846 · GitHub
[go: up one dir, main page]

Skip to content

Commit 00b6846

Browse files
feature #34871 [HttpClient] Allow pass array of callable to the mocking http client (Koc)
This PR was merged into the 5.1-dev branch. Discussion ---------- [HttpClient] Allow pass array of callable to the mocking http client | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | - | License | MIT | Doc PR | not yet For the now MockHttpClient allows pass closure as response factory. It useful for tests to perform assertions that expected request was sent. But If we are sending multiple sequental requests then it became a little bit tricky to perform assertions: ```php <?php $requestIndex = 0; $expectedRequest = function ($method, $url, $options) use (&$requestIndex) { switch (++$requestIndex) { case 1: $this->assertSame('GET', $method); $this->assertSame('https://example.com/api/v1/customer', $url); return new MockResponse(CustomerFixture::CUSTOMER_RESPONSE); case 2: $this->assertSame('POST', $method); $this->assertSame('https://example.com/api/v1/customer/1/products', $url); $this->assertJsonStringEqualsJsonFile(CustomerFixture::CUSTOMER_PRODUCT_PAYLOAD, $options['json']); return new MockResponse(); default: throw new \InvalidArgumentException('Too much requests'); } }; $client = new MockHttpClient($expectedRequest); static::$container->set('http_client.example', $client); $commandTester->execute(['--since' => '2019-01-01 00:05:00', '--until' => '2019-01-01 00:35:00']); $this->assertSame(2, $requestIndex, 'All expected requests was sent.'); ``` This PR introduces possibility to define multiple callable response factories and `getSentRequestsCount` method to make sure that each factory was called: ```php <?php $expectedRequests = [ function ($method, $url, $options) { $this->assertSame('GET', $method); $this->assertSame('https://example.com/api/v1/customer', $url); return new MockResponse(CustomerFixture::CUSTOMER_RESPONSE); }, function ($method, $url, $options) { $this->assertSame('POST', $method); $this->assertSame('https://example.com/api/v1/customer/1/products', $url); $this->assertJsonStringEqualsJsonFile(CustomerFixture::CUSTOMER_PRODUCT_PAYLOAD, $options['json']); return new MockResponse(); }, ]; $client = new MockHttpClient($expectedRequest); static::$container->set('http_client.example', $client); $commandTester->execute(['--since' => '2019-01-01 00:05:00', '--until' => '2019-01-01 00:35:00']); $this->assertSame(2, $client->getSentRequestsCount(), 'All expected requests was sent.'); ``` Also it adds a lot of tests. Commits ------- a36797d Allow pass array of callable to the mocking http client
2 parents 985f64b + a36797d commit 00b6846

File tree

2 files changed

+131
-2
lines changed

2 files changed

+131
-2
lines changed

src/Symfony/Component/HttpClient/MockHttpClient.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,10 @@ class MockHttpClient implements HttpClientInterface
2929

3030
private $responseFactory;
3131
private $baseUri;
32+
private $requestsCount = 0;
3233

3334
/**
34-
* @param callable|ResponseInterface|ResponseInterface[]|iterable|null $responseFactory
35+
* @param callable|callable[]|ResponseInterface|ResponseInterface[]|iterable|null $responseFactory
3536
*/
3637
public function __construct($responseFactory = null, string $baseUri = null)
3738
{
@@ -64,9 +65,11 @@ public function request(string $method, string $url, array $options = []): Respo
6465
} elseif (!$this->responseFactory->valid()) {
6566
throw new TransportException('The response factory iterator passed to MockHttpClient is empty.');
6667
} else {
67-
$response = $this->responseFactory->current();
68+
$responseFactory = $this->responseFactory->current();
69+
$response = \is_callable($responseFactory) ? $responseFactory($method, $url, $options) : $responseFactory;
6870
$this->responseFactory->next();
6971
}
72+
++$this->requestsCount;
7073

7174
return MockResponse::fromRequest($method, $url, $options, $response);
7275
}
@@ -84,4 +87,9 @@ public function stream($responses, float $timeout = null): ResponseStreamInterfa
8487

8588
return new ResponseStream(MockResponse::stream($responses, $timeout));
8689
}
90+
91+
public function getRequestsCount(): int
92+
{
93+
return $this->requestsCount;
94+
}
8795
}

src/Symfony/Component/HttpClient/Tests/MockHttpClientTest.php

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,127 @@
2222

2323
class MockHttpClientTest extends HttpClientTestCase
2424
{
25+
/**
26+
* @dataProvider mockingProvider
27+
*/
28+
public function testMocking($factory, array $expectedResponses)
29+
{
30+
$client = new MockHttpClient($factory, 'https://example.com/');
31+
$this->assertSame(0, $client->getRequestsCount());
32+
33+
$urls = ['/foo', '/bar'];
34+
foreach ($urls as $i => $url) {
35+
$response = $client->request('POST', $url, ['body' => 'payload']);
36+
$this->assertEquals($expectedResponses[$i], $response->getContent());
37+
}
38+
39+
$this->assertSame(2, $client->getRequestsCount());
40+
}
41+
42+
public function mockingProvider(): iterable
43+
{
44+
yield 'callable' => [
45+
static function (string $method, string $url, array $options = []) {
46+
return new MockResponse($method.': '.$url.' (body='.$options['body'].')');
47+
},
48+
[
49+
'POST: https://example.com/foo (body=payload)',
50+
'POST: https://example.com/bar (body=payload)',
51+
],
52+
];
53+
54+
yield 'array of callable' => [
55+
[
56+
static function (string $method, string $url, array $options = []) {
57+
return new MockResponse($method.': '.$url.' (body='.$options['body'].') [1]');
58+
},
59+
static function (string $method, string $url, array $options = []) {
60+
return new MockResponse($method.': '.$url.' (body='.$options['body'].') [2]');
61+
},
62+
],
63+
[
64+
'POST: https://example.com/foo (body=payload) [1]',
65+
'POST: https://example.com/bar (body=payload) [2]',
66+
],
67+
];
68+
69+
yield 'array of response objects' => [
70+
[
71+
new MockResponse('static response [1]'),
72+
new MockResponse('static response [2]'),
73+
],
74+
[
75+
'static response [1]',
76+
'static response [2]',
77+
],
78+
];
79+
80+
yield 'iterator' => [
81+
new \ArrayIterator(
82+
[
83+
new MockResponse('static response [1]'),
84+
new MockResponse('static response [2]'),
85+
]
86+
),
87+
[
88+
'static response [1]',
89+
'static response [2]',
90+
],
91+
];
92+
93+
yield 'null' => [
94+
null,
95+
[
96+
'',
97+
'',
98+
],
99+
];
100+
}
101+
102+
/**
103+
* @dataProvider transportExceptionProvider
104+
*/
105+
public function testTransportExceptionThrowsIfPerformedMoreRequestsThanConfigured($factory)
106+
{
107+
$client = new MockHttpClient($factory, 'https://example.com/');
108+
109+
$client->request('POST', '/foo');
110+
$client->request('POST', '/foo');
111+
112+
$this->expectException(TransportException::class);
113+
$client->request('POST', '/foo');
114+
}
115+
116+
public function transportExceptionProvider(): iterable
117+
{
118+
yield 'array of callable' => [
119+
[
120+
static function (string $method, string $url, array $options = []) {
121+
return new MockResponse();
122+
},
123+
static function (string $method, string $url, array $options = []) {
124+
return new MockResponse();
125+
},
126+
],
127+
];
128+
129+
yield 'array of response objects' => [
130+
[
131+
new MockResponse(),
132+
new MockResponse(),
133+
],
134+
];
135+
136+
yield 'iterator' => [
137+
new \ArrayIterator(
138+
[
139+
new MockResponse(),
140+
new MockResponse(),
141+
]
142+
),
143+
];
144+
}
145+
25146
protected function getHttpClient(string $testCase): HttpClientInterface
26147
{
27148
$responses = [];

0 commit comments

Comments
 (0)
0