Skip to content

Commit 3a16803

Browse files
[HttpClient] add MockHttpClient
1 parent f88cb07 commit 3a16803

File tree

10 files changed

+545
-53
lines changed

10 files changed

+545
-53
lines changed

src/Symfony/Component/HttpClient/Chunk/ErrorChunk.php

+10-7
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,14 @@
2121
*/
2222
class ErrorChunk implements ChunkInterface
2323
{
24-
protected $didThrow;
25-
24+
private $didThrow = false;
2625
private $offset;
2726
private $errorMessage;
2827
private $error;
2928

30-
/**
31-
* @param bool &$didThrow Allows monitoring when the $error has been thrown or not
32-
*/
33-
public function __construct(bool &$didThrow, int $offset, \Throwable $error = null)
29+
public function __construct(int $offset, \Throwable $error = null)
3430
{
3531
$didThrow = false;
36-
$this->didThrow = &$didThrow;
3732
$this->offset = $offset;
3833
$this->error = $error;
3934
$this->errorMessage = null !== $error ? $error->getMessage() : 'Reading from the response stream reached the inactivity timeout.';
@@ -96,6 +91,14 @@ public function getError(): ?string
9691
return $this->errorMessage;
9792
}
9893

94+
/**
95+
* @return bool Whether the wrapped error has been thrown or not
96+
*/
97+
public function didThrow(): bool
98+
{
99+
return $this->didThrow;
100+
}
101+
99102
public function __destruct()
100103
{
101104
if (!$this->didThrow) {

src/Symfony/Component/HttpClient/HttpClientTrait.php

+3-1
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ private static function prepareRequest(?string $method, ?string $url, array $opt
117117

118118
// Finalize normalization of options
119119
$options['headers'] = $headers;
120-
$options['http_version'] = (string) ($options['http_version'] ?? '');
120+
$options['http_version'] = (string) ($options['http_version'] ?? '') ?: null;
121121
$options['timeout'] = (float) ($options['timeout'] ?? ini_get('default_socket_timeout'));
122122

123123
return [$url, $options];
@@ -128,6 +128,8 @@ private static function prepareRequest(?string $method, ?string $url, array $opt
128128
*/
129129
private static function mergeDefaultOptions(array $options, array $defaultOptions, bool $allowExtraOptions = false): array
130130
{
131+
unset($options['raw_headers'], $defaultOptions['raw_headers']);
132+
131133
$options['headers'] = self::normalizeHeaders($options['headers'] ?? []);
132134

133135
if ($defaultOptions['headers'] ?? false) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\HttpClient;
13+
14+
use Symfony\Component\HttpClient\Exception\TransportException;
15+
use Symfony\Component\HttpClient\Response\MockResponse;
16+
use Symfony\Component\HttpClient\Response\ResponseStream;
17+
use Symfony\Contracts\HttpClient\HttpClientInterface;
18+
use Symfony\Contracts\HttpClient\ResponseInterface;
19+
use Symfony\Contracts\HttpClient\ResponseStreamInterface;
20+
21+
/**
22+
* A test-friendly HttpClient that doesn't make actual HTTP requests.
23+
*
24+
* @author Nicolas Grekas <p@tchwork.com>
25+
*/
26+
class MockHttpClient implements HttpClientInterface
27+
{
28+
use HttpClientTrait;
29+
30+
private $responseFactory;
31+
private $baseUri;
32+
33+
/**
34+
* @param callable|ResponseInterface[]|iterable $responseFactory
35+
*/
36+
public function __construct($responseFactory, string $baseUri = null)
37+
{
38+
if (!\is_callable($responseFactory) && !$responseFactory instanceof \Iterator) {
39+
$responseFactory = (function () use ($responseFactory) {
40+
yield from $responseFactory;
41+
})();
42+
}
43+
44+
$this->responseFactory = $responseFactory;
45+
$this->baseUri = $baseUri;
46+
}
47+
48+
/**
49+
* {@inheritdoc}
50+
*/
51+
public function request(string $method, string $url, array $options = []): ResponseInterface
52+
{
53+
[$url, $options] = $this->prepareRequest($method, $url, $options, ['base_uri' => $this->baseUri], true);
54+
$url = implode('', $url);
55+
56+
if (\is_callable($this->responseFactory)) {
57+
$response = ($this->responseFactory)($method, $url, $options);
58+
} elseif (!$this->responseFactory->valid()) {
59+
throw new TransportException('The response factory iterator passed to MockHttpClient is empty.');
60+
} else {
61+
$response = $this->responseFactory->current();
62+
$this->responseFactory->next();
63+
}
64+
65+
return MockResponse::fromRequest($method, $url, $options, $response);
66+
}
67+
68+
/**
69+
* {@inheritdoc}
70+
*/
71+
public function stream($responses, float $timeout = null): ResponseStreamInterface
72+
{
73+
if ($responses instanceof ResponseInterface) {
74+
$responses = [$responses];
75+
} elseif (!\is_iterable($responses)) {
76+
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)));
77+
}
78+
79+
return new ResponseStream(MockResponse::stream($responses, $timeout));
80+
}
81+
}

0 commit comments

Comments
 (0)