feature #30604 [HttpClient] add MockHttpClient (nicolas-grekas)

This PR was merged into the 4.3-dev branch.

Discussion
----------

[HttpClient] add MockHttpClient

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | -
| License       | MIT
| Doc PR        | -

This PR introduces `MockHttpClient` and `MockResponse`, to be used for testing classes that need an HTTP client without making actual HTTP requests.

`MockHttpClient` is configured via its constructor: you provide it either with an iterable or a callable, and these will be used to provide responses as the consumer requests them.

Example:
```php
$responses = [
    new MockResponse($body1, $info1),
    new MockResponse($body2, $info2),
];

$client = new MockHttpClient($responses);
$response1 = $client->request(...); // created from $responses[0]
$response2 = $client->request(...); // created from $responses[1]
```

Or alternatively:
```php
$callback = function ($method, $url, $options) {
    return new MockResponse(...);
};

$client = new MockHttpClient($callback);
$response = $client->request(...); // calls $callback internal
```

The responses provided to the client don't have to be instances of `MockResponse` - any `ResponseInterface` works (e.g. `$this->getMockBuilder(ResponseInterface::class)->getMock()`).

Using `MockResponse` allows simulating chunked responses and timeouts:
```php
$body = function () {
    yield 'hello';
    yield ''; // the empty string is turned into a timeout so that they are easy to test
    yield 'world';
};
$mockResponse = new Mockresponse($body);
```

Last but not least, the implementation simulates the full lifecycle of a properly behaving `HttpClientInterface` contracts implementation: error handling, progress function, etc. This is "proved" by `MockHttpClientTest`, who implements and passes the reference test suite in `HttpClientTestCase`.

Commits
-------

8fd7584158 [HttpClient] add MockHttpClient
This commit is contained in:
Fabien Potencier 2019-03-19 19:41:07 +01:00
commit d5d1b50cf7
10 changed files with 549 additions and 53 deletions

View File

@ -21,19 +21,14 @@ use Symfony\Contracts\HttpClient\ChunkInterface;
*/
class ErrorChunk implements ChunkInterface
{
protected $didThrow;
private $didThrow = false;
private $offset;
private $errorMessage;
private $error;
/**
* @param bool &$didThrow Allows monitoring when the $error has been thrown or not
*/
public function __construct(bool &$didThrow, int $offset, \Throwable $error = null)
public function __construct(int $offset, \Throwable $error = null)
{
$didThrow = false;
$this->didThrow = &$didThrow;
$this->offset = $offset;
$this->error = $error;
$this->errorMessage = null !== $error ? $error->getMessage() : 'Reading from the response stream reached the inactivity timeout.';
@ -96,6 +91,14 @@ class ErrorChunk implements ChunkInterface
return $this->errorMessage;
}
/**
* @return bool Whether the wrapped error has been thrown or not
*/
public function didThrow(): bool
{
return $this->didThrow;
}
public function __destruct()
{
if (!$this->didThrow) {

View File

@ -117,7 +117,7 @@ trait HttpClientTrait
// Finalize normalization of options
$options['headers'] = $headers;
$options['http_version'] = (string) ($options['http_version'] ?? '');
$options['http_version'] = (string) ($options['http_version'] ?? '') ?: null;
$options['timeout'] = (float) ($options['timeout'] ?? ini_get('default_socket_timeout'));
return [$url, $options];
@ -128,6 +128,8 @@ trait HttpClientTrait
*/
private static function mergeDefaultOptions(array $options, array $defaultOptions, bool $allowExtraOptions = false): array
{
unset($options['raw_headers'], $defaultOptions['raw_headers']);
$options['headers'] = self::normalizeHeaders($options['headers'] ?? []);
if ($defaultOptions['headers'] ?? false) {

View File

@ -0,0 +1,85 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpClient;
use Symfony\Component\HttpClient\Exception\TransportException;
use Symfony\Component\HttpClient\Response\MockResponse;
use Symfony\Component\HttpClient\Response\ResponseStream;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Contracts\HttpClient\ResponseInterface;
use Symfony\Contracts\HttpClient\ResponseStreamInterface;
/**
* A test-friendly HttpClient that doesn't make actual HTTP requests.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class MockHttpClient implements HttpClientInterface
{
use HttpClientTrait;
private $responseFactory;
private $baseUri;
/**
* @param callable|ResponseInterface|ResponseInterface[]|iterable $responseFactory
*/
public function __construct($responseFactory, string $baseUri = null)
{
if ($responseFactory instanceof ResponseInterface) {
$responseFactory = [$responseFactory];
}
if (!\is_callable($responseFactory) && !$responseFactory instanceof \Iterator) {
$responseFactory = (function () use ($responseFactory) {
yield from $responseFactory;
})();
}
$this->responseFactory = $responseFactory;
$this->baseUri = $baseUri;
}
/**
* {@inheritdoc}
*/
public function request(string $method, string $url, array $options = []): ResponseInterface
{
[$url, $options] = $this->prepareRequest($method, $url, $options, ['base_uri' => $this->baseUri], true);
$url = implode('', $url);
if (\is_callable($this->responseFactory)) {
$response = ($this->responseFactory)($method, $url, $options);
} elseif (!$this->responseFactory->valid()) {
throw new TransportException('The response factory iterator passed to MockHttpClient is empty.');
} else {
$response = $this->responseFactory->current();
$this->responseFactory->next();
}
return MockResponse::fromRequest($method, $url, $options, $response);
}
/**
* {@inheritdoc}
*/
public function stream($responses, float $timeout = null): ResponseStreamInterface
{
if ($responses instanceof ResponseInterface) {
$responses = [$responses];
} elseif (!\is_iterable($responses)) {
throw new \TypeError(sprintf('%s() expects parameter 1 to be an iterable of MockResponse objects, %s given.', __METHOD__, \is_object($responses) ? \get_class($responses) : \gettype($responses)));
}
return new ResponseStream(MockResponse::stream($responses, $timeout));
}
}

View File

@ -0,0 +1,265 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpClient\Response;
use Symfony\Component\HttpClient\Chunk\ErrorChunk;
use Symfony\Component\HttpClient\Chunk\FirstChunk;
use Symfony\Component\HttpClient\Exception\InvalidArgumentException;
use Symfony\Component\HttpClient\Exception\TransportException;
use Symfony\Contracts\HttpClient\ResponseInterface;
/**
* A test-friendly response.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class MockResponse implements ResponseInterface
{
use ResponseTrait;
private $body;
private static $mainMulti;
private static $idSequence = 0;
/**
* @param string|string[]|iterable $body The response body as a string or an iterable of strings,
* yielding an empty string simulates a timeout,
* exceptions are turned to TransportException
*
* @see ResponseInterface::getInfo() for possible info, e.g. "raw_headers"
*/
public function __construct($body = '', array $info = [])
{
$this->body = \is_iterable($body) ? $body : (string) $body;
$this->info = $info + $this->info;
}
/**
* {@inheritdoc}
*/
public function getInfo(string $type = null)
{
return null !== $type ? $this->info[$type] ?? null : $this->info;
}
/**
* {@inheritdoc}
*/
protected function close(): void
{
$this->body = [];
}
/**
* @internal
*/
public static function fromRequest(string $method, string $url, array $options, ResponseInterface $mock): self
{
$response = new self([]);
$response->id = ++self::$idSequence;
$response->content = ($options['buffer'] ?? true) ? fopen('php://temp', 'w+') : null;
$response->initializer = static function (self $response) {
if (null !== $response->info['error']) {
throw new TransportException($response->info['error']);
}
if (\is_array($response->body[0] ?? null)) {
// Consume the first chunk if it's not yielded yet
self::stream([$response])->current();
}
};
$response->info['redirect_count'] = 0;
$response->info['redirect_url'] = null;
$response->info['start_time'] = microtime(true);
$response->info['http_method'] = $method;
$response->info['http_code'] = 0;
$response->info['user_data'] = $options['user_data'] ?? null;
$response->info['url'] = $url;
self::writeRequest($response, $options, $mock);
$response->body[] = [$options, $mock];
return $response;
}
/**
* {@inheritdoc}
*/
protected static function schedule(self $response, array &$runningResponses): void
{
if (!$response->id) {
throw new InvalidArgumentException('MockResponse instances must be issued by MockHttpClient before processing.');
}
$multi = self::$mainMulti ?? self::$mainMulti = (object) [
'handlesActivity' => [],
'openHandles' => [],
];
if (!isset($runningResponses[0])) {
$runningResponses[0] = [$multi, []];
}
$runningResponses[0][1][$response->id] = $response;
}
/**
* {@inheritdoc}
*/
protected static function perform(\stdClass $multi, array &$responses): void
{
foreach ($responses as $response) {
$id = $response->id;
if (!$response->body) {
// Last chunk
$multi->handlesActivity[$id][] = null;
$multi->handlesActivity[$id][] = null !== $response->info['error'] ? new TransportException($response->info['error']) : null;
} elseif (null === $chunk = array_shift($response->body)) {
// Last chunk
$multi->handlesActivity[$id][] = null;
$multi->handlesActivity[$id][] = array_shift($response->body);
} elseif (\is_array($chunk)) {
// First chunk
try {
$offset = 0;
$chunk[1]->getStatusCode();
$response->headers = $chunk[1]->getHeaders(false);
$multi->handlesActivity[$id][] = new FirstChunk();
self::readResponse($response, $chunk[0], $chunk[1], $offset);
} catch (\Throwable $e) {
$multi->handlesActivity[$id][] = null;
$multi->handlesActivity[$id][] = $e;
}
} else {
// Data or timeout chunk
$multi->handlesActivity[$id][] = $chunk;
if (\is_string($chunk) && null !== $response->content) {
// Buffer response body
fwrite($response->content, $chunk);
}
}
}
}
/**
* {@inheritdoc}
*/
protected static function select(\stdClass $multi, float $timeout): int
{
return 42;
}
/**
* Simulates sending the request.
*/
private static function writeRequest(self $response, array $options, ResponseInterface $mock)
{
$onProgress = $options['on_progress'] ?? static function () {};
$response->info += $mock->getInfo() ?: [];
// simulate "size_upload" if it is set
if (isset($response->info['size_upload'])) {
$response->info['size_upload'] = 0.0;
}
// simulate "total_time" if it is set
if (isset($response->info['total_time'])) {
$response->info['total_time'] = microtime(true) - $response->info['start_time'];
}
// "notify" DNS resolution
$onProgress(0, 0, $response->info);
// consume the request body
if (\is_resource($body = $options['body'] ?? '')) {
$data = stream_get_contents($body);
if (isset($response->info['size_upload'])) {
$response->info['size_upload'] += \strlen($data);
}
} elseif ($body instanceof \Closure) {
while ('' !== $data = $body(16372)) {
if (!\is_string($data)) {
throw new TransportException(sprintf('Return value of the "body" option callback must be string, %s returned.', \gettype($data)));
}
// "notify" upload progress
if (isset($response->info['size_upload'])) {
$response->info['size_upload'] += \strlen($data);
}
$onProgress(0, 0, $response->info);
}
}
}
/**
* Simulates reading the response.
*/
private static function readResponse(self $response, array $options, ResponseInterface $mock, int &$offset)
{
$onProgress = $options['on_progress'] ?? static function () {};
// populate info related to headers
$info = $mock->getInfo() ?: [];
$response->info['http_code'] = ($info['http_code'] ?? 0) ?: $mock->getStatusCode(false) ?: 200;
$response->addRawHeaders($info['raw_headers'] ?? [], $response->info, $response->headers);
$dlSize = (int) ($response->headers['content-length'][0] ?? 0);
$response->info = [
'start_time' => $response->info['start_time'],
'user_data' => $response->info['user_data'],
'http_code' => $response->info['http_code'],
] + $info + $response->info;
if (isset($response->info['total_time'])) {
$response->info['total_time'] = microtime(true) - $response->info['start_time'];
}
// "notify" headers arrival
$onProgress(0, $dlSize, $response->info);
// cast response body to activity list
$body = $mock instanceof self ? $mock->body : $mock->getContent(false);
if (!\is_string($body)) {
foreach ($body as $chunk) {
if ('' === $chunk = (string) $chunk) {
// simulate a timeout
$response->body[] = new ErrorChunk($offset);
} else {
$response->body[] = $chunk;
$offset += \strlen($chunk);
// "notify" download progress
$onProgress($offset, $dlSize, $response->info);
}
}
} elseif ('' !== $body) {
$response->body[] = $body;
$offset = \strlen($body);
}
if (isset($response->info['total_time'])) {
$response->info['total_time'] = microtime(true) - $response->info['start_time'];
}
// "notify" completion
$onProgress($offset, $dlSize, $response->info);
if (isset($response->headers['content-length']) && $offset !== $dlSize) {
throw new TransportException(sprintf('Transfer closed with %s bytes remaining to read.', $dlSize - $offset));
}
}
}

View File

@ -147,7 +147,7 @@ final class NativeResponse implements ResponseInterface
$this->inflate = null;
}
$this->multi->openHandles[$this->id] = [$h, $this->buffer, $this->inflate, &$this->content, $this->onProgress, &$this->remaining, &$this->info];
$this->multi->openHandles[$this->id] = [$h, $this->buffer, $this->inflate, $this->content, $this->onProgress, &$this->remaining, &$this->info];
$this->multi->handlesActivity[$this->id] = [new FirstChunk()];
}

View File

@ -259,9 +259,7 @@ trait ResponseTrait
foreach ($responses as $j => $response) {
$timeoutMax = $timeout ?? max($timeoutMax, $response->timeout);
$timeoutMin = min($timeoutMin, $response->timeout, 1);
// ErrorChunk instances will set $didThrow to true when the
// exception they wrap has been thrown after yielding
$chunk = $didThrow = false;
$chunk = false;
if (isset($multi->handlesActivity[$j])) {
// no-op
@ -269,7 +267,7 @@ trait ResponseTrait
unset($responses[$j]);
continue;
} elseif ($isTimeout) {
$multi->handlesActivity[$j] = [new ErrorChunk($didThrow, $response->offset)];
$multi->handlesActivity[$j] = [new ErrorChunk($response->offset)];
} else {
continue;
}
@ -293,7 +291,7 @@ trait ResponseTrait
throw $e;
}
$chunk = new ErrorChunk($didThrow, $response->offset, $e);
$chunk = new ErrorChunk($response->offset, $e);
} else {
$chunk = new LastChunk($response->offset);
}
@ -310,7 +308,7 @@ trait ResponseTrait
if ($chunk instanceof FirstChunk && null === $response->initializer) {
// Ensure the HTTP status code is always checked
$response->getHeaders(true);
} elseif ($chunk instanceof ErrorChunk && !$didThrow) {
} elseif ($chunk instanceof ErrorChunk && !$chunk->didThrow()) {
// Ensure transport exceptions are always thrown
$chunk->getContent();
}

View File

@ -20,7 +20,7 @@ use Symfony\Contracts\HttpClient\Test\HttpClientTestCase;
*/
class CurlHttpClientTest extends HttpClientTestCase
{
protected function getHttpClient(): HttpClientInterface
protected function getHttpClient(string $testCase): HttpClientInterface
{
return new CurlHttpClient();
}

View File

@ -0,0 +1,130 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpClient\Tests;
use Symfony\Component\HttpClient\Exception\TransportException;
use Symfony\Component\HttpClient\MockHttpClient;
use Symfony\Component\HttpClient\NativeHttpClient;
use Symfony\Component\HttpClient\Response\MockResponse;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Contracts\HttpClient\ResponseInterface;
use Symfony\Contracts\HttpClient\Test\HttpClientTestCase;
class MockHttpClientTest extends HttpClientTestCase
{
protected function getHttpClient(string $testCase): HttpClientInterface
{
$responses = [];
$headers = [
'Host: localhost:8057',
'Content-Type: application/json',
];
$body = '{
"SERVER_PROTOCOL": "HTTP/1.1",
"SERVER_NAME": "127.0.0.1",
"REQUEST_URI": "/",
"REQUEST_METHOD": "GET",
"HTTP_FOO": "baR",
"HTTP_HOST": "localhost:8057"
}';
$client = new NativeHttpClient();
switch ($testCase) {
default:
return new MockHttpClient(function (string $method, string $url, array $options) use ($client) {
try {
// force the request to be completed so that we don't test side effects of the transport
$response = $client->request($method, $url, $options);
$content = $response->getContent(false);
return new MockResponse($content, $response->getInfo());
} catch (\Throwable $e) {
$this->fail($e->getMessage());
}
});
case 'testUnsupportedOption':
$this->markTestSkipped('MockHttpClient accepts any options by default');
break;
case 'testChunkedEncoding':
$this->markTestSkipped("MockHttpClient doesn't dechunk");
break;
case 'testGzipBroken':
$this->markTestSkipped("MockHttpClient doesn't unzip");
break;
case 'testDestruct':
$this->markTestSkipped("MockHttpClient doesn't timeout on destruct");
break;
case 'testGetRequest':
array_unshift($headers, 'HTTP/1.1 200 OK');
$responses[] = new MockResponse($body, ['raw_headers' => $headers]);
$headers = [
'Host: localhost:8057',
'Content-Length: 1000',
'Content-Type: application/json',
];
$responses[] = new MockResponse($body, ['raw_headers' => $headers]);
break;
case 'testDnsError':
$mock = $this->getMockBuilder(ResponseInterface::class)->getMock();
$mock->expects($this->any())
->method('getStatusCode')
->willThrowException(new TransportException('DSN error'));
$mock->expects($this->any())
->method('getInfo')
->willReturn([]);
$responses[] = $mock;
$responses[] = $mock;
break;
case 'testBadRequestBody':
case 'testOnProgressCancel':
case 'testOnProgressError':
$responses[] = new MockResponse($body, ['raw_headers' => $headers]);
break;
case 'testTimeoutOnAccess':
$mock = $this->getMockBuilder(ResponseInterface::class)->getMock();
$mock->expects($this->any())
->method('getHeaders')
->willThrowException(new TransportException('Timeout'));
$responses[] = $mock;
break;
case 'testResolve':
$responses[] = new MockResponse($body, ['raw_headers' => $headers]);
$responses[] = new MockResponse($body, ['raw_headers' => $headers]);
$responses[] = $client->request('GET', 'http://symfony.com:8057/');
break;
case 'testTimeoutOnStream':
case 'testUncheckedTimeoutThrows':
$body = ['<1>', '', '<2>'];
$responses[] = new MockResponse($body, ['raw_headers' => $headers]);
break;
}
return new MockHttpClient($responses);
}
}

View File

@ -17,7 +17,7 @@ use Symfony\Contracts\HttpClient\Test\HttpClientTestCase;
class NativeHttpClientTest extends HttpClientTestCase
{
protected function getHttpClient(): HttpClientInterface
protected function getHttpClient(string $testCase): HttpClientInterface
{
return new NativeHttpClient();
}

View File

@ -31,11 +31,11 @@ abstract class HttpClientTestCase extends TestCase
TestHttpServer::start();
}
abstract protected function getHttpClient(): HttpClientInterface;
abstract protected function getHttpClient(string $testCase): HttpClientInterface;
public function testGetRequest()
{
$client = $this->getHttpClient();
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057', [
'headers' => ['Foo' => 'baR'],
'user_data' => $data = new \stdClass(),
@ -74,7 +74,7 @@ abstract class HttpClientTestCase extends TestCase
public function testNonBufferedGetRequest()
{
$client = $this->getHttpClient();
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057', [
'buffer' => false,
'headers' => ['Foo' => 'baR'],
@ -89,7 +89,7 @@ abstract class HttpClientTestCase extends TestCase
public function testUnsupportedOption()
{
$client = $this->getHttpClient();
$client = $this->getHttpClient(__FUNCTION__);
$this->expectException(\InvalidArgumentException::class);
$client->request('GET', 'http://localhost:8057', [
@ -99,7 +99,7 @@ abstract class HttpClientTestCase extends TestCase
public function testHttpVersion()
{
$client = $this->getHttpClient();
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057', [
'http_version' => 1.0,
]);
@ -116,7 +116,7 @@ abstract class HttpClientTestCase extends TestCase
public function testChunkedEncoding()
{
$client = $this->getHttpClient();
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057/chunked');
$this->assertSame(['chunked'], $response->getHeaders()['transfer-encoding']);
@ -130,7 +130,7 @@ abstract class HttpClientTestCase extends TestCase
public function testClientError()
{
$client = $this->getHttpClient();
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057/404');
$client->stream($response)->valid();
@ -156,7 +156,7 @@ abstract class HttpClientTestCase extends TestCase
public function testIgnoreErrors()
{
$client = $this->getHttpClient();
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057/404');
$this->assertSame(404, $response->getStatusCode());
@ -164,7 +164,7 @@ abstract class HttpClientTestCase extends TestCase
public function testDnsError()
{
$client = $this->getHttpClient();
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057/301/bad-tld');
try {
@ -201,7 +201,7 @@ abstract class HttpClientTestCase extends TestCase
public function testInlineAuth()
{
$client = $this->getHttpClient();
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://foo:bar%3Dbar@localhost:8057');
$body = $response->toArray();
@ -210,9 +210,22 @@ abstract class HttpClientTestCase extends TestCase
$this->assertSame('bar=bar', $body['PHP_AUTH_PW']);
}
public function testBadRequestBody()
{
$client = $this->getHttpClient(__FUNCTION__);
$this->expectException(TransportExceptionInterface::class);
$response = $client->request('POST', 'http://localhost:8057/', [
'body' => function () { yield []; },
]);
$response->getStatusCode();
}
public function testRedirects()
{
$client = $this->getHttpClient();
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('POST', 'http://localhost:8057/301', [
'auth_basic' => 'foo:bar',
'body' => function () {
@ -248,7 +261,7 @@ abstract class HttpClientTestCase extends TestCase
public function testRelativeRedirects()
{
$client = $this->getHttpClient();
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057/302/relative');
$body = $response->toArray();
@ -266,7 +279,7 @@ abstract class HttpClientTestCase extends TestCase
public function testRedirect307()
{
$client = $this->getHttpClient();
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('POST', 'http://localhost:8057/307', [
'body' => function () {
@ -288,7 +301,7 @@ abstract class HttpClientTestCase extends TestCase
public function testMaxRedirects()
{
$client = $this->getHttpClient();
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057/301', [
'max_redirects' => 1,
'auth_basic' => 'foo:bar',
@ -322,7 +335,7 @@ abstract class HttpClientTestCase extends TestCase
public function testStream()
{
$client = $this->getHttpClient();
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057');
$chunks = $client->stream($response);
@ -354,7 +367,7 @@ abstract class HttpClientTestCase extends TestCase
public function testAddToStream()
{
$client = $this->getHttpClient();
$client = $this->getHttpClient(__FUNCTION__);
$r1 = $client->request('GET', 'http://localhost:8057');
@ -385,7 +398,7 @@ abstract class HttpClientTestCase extends TestCase
public function testCompleteTypeError()
{
$client = $this->getHttpClient();
$client = $this->getHttpClient(__FUNCTION__);
$this->expectException(\TypeError::class);
$client->stream(123);
@ -393,7 +406,7 @@ abstract class HttpClientTestCase extends TestCase
public function testOnProgress()
{
$client = $this->getHttpClient();
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('POST', 'http://localhost:8057/post', [
'headers' => ['Content-Length' => 14],
'body' => 'foo=0123456789',
@ -411,7 +424,7 @@ abstract class HttpClientTestCase extends TestCase
public function testPostJson()
{
$client = $this->getHttpClient();
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('POST', 'http://localhost:8057/post', [
'json' => ['foo' => 'bar'],
@ -426,7 +439,7 @@ abstract class HttpClientTestCase extends TestCase
public function testPostArray()
{
$client = $this->getHttpClient();
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('POST', 'http://localhost:8057/post', [
'body' => ['foo' => 'bar'],
@ -437,7 +450,7 @@ abstract class HttpClientTestCase extends TestCase
public function testPostResource()
{
$client = $this->getHttpClient();
$client = $this->getHttpClient(__FUNCTION__);
$h = fopen('php://temp', 'w+');
fwrite($h, 'foo=0123456789');
@ -454,7 +467,7 @@ abstract class HttpClientTestCase extends TestCase
public function testPostCallback()
{
$client = $this->getHttpClient();
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('POST', 'http://localhost:8057/post', [
'body' => function () {
@ -470,7 +483,7 @@ abstract class HttpClientTestCase extends TestCase
public function testOnProgressCancel()
{
$client = $this->getHttpClient();
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057/timeout-body', [
'on_progress' => function ($dlNow) {
if (0 < $dlNow) {
@ -494,7 +507,7 @@ abstract class HttpClientTestCase extends TestCase
public function testOnProgressError()
{
$client = $this->getHttpClient();
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057/timeout-body', [
'on_progress' => function ($dlNow) {
if (0 < $dlNow) {
@ -518,7 +531,7 @@ abstract class HttpClientTestCase extends TestCase
public function testResolve()
{
$client = $this->getHttpClient();
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://symfony.com:8057/', [
'resolve' => ['symfony.com' => '127.0.0.1'],
]);
@ -533,7 +546,7 @@ abstract class HttpClientTestCase extends TestCase
public function testTimeoutOnAccess()
{
$client = $this->getHttpClient();
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057/timeout-header', [
'timeout' => 0.1,
]);
@ -545,7 +558,7 @@ abstract class HttpClientTestCase extends TestCase
public function testTimeoutOnStream()
{
usleep(300000); // wait for the previous test to release the server
$client = $this->getHttpClient();
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057/timeout-body');
$this->assertSame(200, $response->getStatusCode());
@ -577,7 +590,7 @@ abstract class HttpClientTestCase extends TestCase
public function testUncheckedTimeoutThrows()
{
$client = $this->getHttpClient();
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057/timeout-body');
$chunks = $client->stream([$response], 0.1);
@ -589,7 +602,7 @@ abstract class HttpClientTestCase extends TestCase
public function testDestruct()
{
$client = $this->getHttpClient();
$client = $this->getHttpClient(__FUNCTION__);
$downloaded = 0;
$start = microtime(true);
@ -603,7 +616,7 @@ abstract class HttpClientTestCase extends TestCase
public function testProxy()
{
$client = $this->getHttpClient();
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057/', [
'proxy' => 'http://localhost:8057',
]);
@ -625,7 +638,7 @@ abstract class HttpClientTestCase extends TestCase
putenv('no_proxy='.$_SERVER['no_proxy'] = 'example.com, localhost');
try {
$client = $this->getHttpClient();
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057/', [
'proxy' => 'http://localhost:8057',
]);
@ -646,7 +659,7 @@ abstract class HttpClientTestCase extends TestCase
*/
public function testAutoEncodingRequest()
{
$client = $this->getHttpClient();
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057');
$this->assertSame(200, $response->getStatusCode());
@ -663,7 +676,7 @@ abstract class HttpClientTestCase extends TestCase
public function testBaseUri()
{
$client = $this->getHttpClient();
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', '../404', [
'base_uri' => 'http://localhost:8057/abc/',
]);
@ -674,7 +687,7 @@ abstract class HttpClientTestCase extends TestCase
public function testQuery()
{
$client = $this->getHttpClient();
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057/?a=a', [
'query' => ['b' => 'b'],
]);
@ -689,7 +702,7 @@ abstract class HttpClientTestCase extends TestCase
*/
public function testUserlandEncodingRequest()
{
$client = $this->getHttpClient();
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057', [
'headers' => ['Accept-Encoding' => 'gzip'],
]);
@ -711,7 +724,7 @@ abstract class HttpClientTestCase extends TestCase
*/
public function testGzipBroken()
{
$client = $this->getHttpClient();
$client = $this->getHttpClient(__FUNCTION__);
$response = $client->request('GET', 'http://localhost:8057/gzip-broken');
$this->expectException(TransportExceptionInterface::class);