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
-------
a36797d60e
Allow pass array of callable to the mocking http client
This commit is contained in:
commit
00b6846978
|
@ -29,9 +29,10 @@ class MockHttpClient implements HttpClientInterface
|
|||
|
||||
private $responseFactory;
|
||||
private $baseUri;
|
||||
private $requestsCount = 0;
|
||||
|
||||
/**
|
||||
* @param callable|ResponseInterface|ResponseInterface[]|iterable|null $responseFactory
|
||||
* @param callable|callable[]|ResponseInterface|ResponseInterface[]|iterable|null $responseFactory
|
||||
*/
|
||||
public function __construct($responseFactory = null, string $baseUri = null)
|
||||
{
|
||||
|
@ -64,9 +65,11 @@ class MockHttpClient implements HttpClientInterface
|
|||
} elseif (!$this->responseFactory->valid()) {
|
||||
throw new TransportException('The response factory iterator passed to MockHttpClient is empty.');
|
||||
} else {
|
||||
$response = $this->responseFactory->current();
|
||||
$responseFactory = $this->responseFactory->current();
|
||||
$response = \is_callable($responseFactory) ? $responseFactory($method, $url, $options) : $responseFactory;
|
||||
$this->responseFactory->next();
|
||||
}
|
||||
++$this->requestsCount;
|
||||
|
||||
return MockResponse::fromRequest($method, $url, $options, $response);
|
||||
}
|
||||
|
@ -84,4 +87,9 @@ class MockHttpClient implements HttpClientInterface
|
|||
|
||||
return new ResponseStream(MockResponse::stream($responses, $timeout));
|
||||
}
|
||||
|
||||
public function getRequestsCount(): int
|
||||
{
|
||||
return $this->requestsCount;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,127 @@ use Symfony\Contracts\HttpClient\ResponseInterface;
|
|||
|
||||
class MockHttpClientTest extends HttpClientTestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider mockingProvider
|
||||
*/
|
||||
public function testMocking($factory, array $expectedResponses)
|
||||
{
|
||||
$client = new MockHttpClient($factory, 'https://example.com/');
|
||||
$this->assertSame(0, $client->getRequestsCount());
|
||||
|
||||
$urls = ['/foo', '/bar'];
|
||||
foreach ($urls as $i => $url) {
|
||||
$response = $client->request('POST', $url, ['body' => 'payload']);
|
||||
$this->assertEquals($expectedResponses[$i], $response->getContent());
|
||||
}
|
||||
|
||||
$this->assertSame(2, $client->getRequestsCount());
|
||||
}
|
||||
|
||||
public function mockingProvider(): iterable
|
||||
{
|
||||
yield 'callable' => [
|
||||
static function (string $method, string $url, array $options = []) {
|
||||
return new MockResponse($method.': '.$url.' (body='.$options['body'].')');
|
||||
},
|
||||
[
|
||||
'POST: https://example.com/foo (body=payload)',
|
||||
'POST: https://example.com/bar (body=payload)',
|
||||
],
|
||||
];
|
||||
|
||||
yield 'array of callable' => [
|
||||
[
|
||||
static function (string $method, string $url, array $options = []) {
|
||||
return new MockResponse($method.': '.$url.' (body='.$options['body'].') [1]');
|
||||
},
|
||||
static function (string $method, string $url, array $options = []) {
|
||||
return new MockResponse($method.': '.$url.' (body='.$options['body'].') [2]');
|
||||
},
|
||||
],
|
||||
[
|
||||
'POST: https://example.com/foo (body=payload) [1]',
|
||||
'POST: https://example.com/bar (body=payload) [2]',
|
||||
],
|
||||
];
|
||||
|
||||
yield 'array of response objects' => [
|
||||
[
|
||||
new MockResponse('static response [1]'),
|
||||
new MockResponse('static response [2]'),
|
||||
],
|
||||
[
|
||||
'static response [1]',
|
||||
'static response [2]',
|
||||
],
|
||||
];
|
||||
|
||||
yield 'iterator' => [
|
||||
new \ArrayIterator(
|
||||
[
|
||||
new MockResponse('static response [1]'),
|
||||
new MockResponse('static response [2]'),
|
||||
]
|
||||
),
|
||||
[
|
||||
'static response [1]',
|
||||
'static response [2]',
|
||||
],
|
||||
];
|
||||
|
||||
yield 'null' => [
|
||||
null,
|
||||
[
|
||||
'',
|
||||
'',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider transportExceptionProvider
|
||||
*/
|
||||
public function testTransportExceptionThrowsIfPerformedMoreRequestsThanConfigured($factory)
|
||||
{
|
||||
$client = new MockHttpClient($factory, 'https://example.com/');
|
||||
|
||||
$client->request('POST', '/foo');
|
||||
$client->request('POST', '/foo');
|
||||
|
||||
$this->expectException(TransportException::class);
|
||||
$client->request('POST', '/foo');
|
||||
}
|
||||
|
||||
public function transportExceptionProvider(): iterable
|
||||
{
|
||||
yield 'array of callable' => [
|
||||
[
|
||||
static function (string $method, string $url, array $options = []) {
|
||||
return new MockResponse();
|
||||
},
|
||||
static function (string $method, string $url, array $options = []) {
|
||||
return new MockResponse();
|
||||
},
|
||||
],
|
||||
];
|
||||
|
||||
yield 'array of response objects' => [
|
||||
[
|
||||
new MockResponse(),
|
||||
new MockResponse(),
|
||||
],
|
||||
];
|
||||
|
||||
yield 'iterator' => [
|
||||
new \ArrayIterator(
|
||||
[
|
||||
new MockResponse(),
|
||||
new MockResponse(),
|
||||
]
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getHttpClient(string $testCase): HttpClientInterface
|
||||
{
|
||||
$responses = [];
|
||||
|
|
Reference in New Issue