Skip to content

[HttpClient] Allow yielding Exception from MockResponse's $body to mock transport errors #44568

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/Symfony/Component/HttpClient/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
CHANGELOG
=========

6.1
---

* Allow yielding `Exception` from MockResponse's `$body` to mock transport errors

5.4
---

Expand Down
10 changes: 7 additions & 3 deletions src/Symfony/Component/HttpClient/Response/MockResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ class MockResponse implements ResponseInterface, StreamableInterface
private static int $idSequence = 0;

/**
* @param string|string[]|iterable $body The response body as a string or an iterable of strings,
* yielding an empty string simulates an idle timeout,
* throwing an exception yields an ErrorChunk
* @param string|iterable<string|\Throwable> $body The response body as a string or an iterable of strings,
* yielding an empty string simulates an idle timeout,
* throwing or yielding an exception yields an ErrorChunk
*
* @see ResponseInterface::getInfo() for possible info, e.g. "response_headers"
*/
Expand Down Expand Up @@ -305,6 +305,10 @@ private static function readResponse(self $response, array $options, ResponseInt
if (!\is_string($body)) {
try {
foreach ($body as $chunk) {
if ($chunk instanceof \Throwable) {
throw $chunk;
}

if ('' === $chunk = (string) $chunk) {
// simulate an idle timeout
$response->body[] = new ErrorChunk($offset, sprintf('Idle timeout reached for "%s".', $response->info['url']));
Expand Down
37 changes: 37 additions & 0 deletions src/Symfony/Component/HttpClient/Tests/MockHttpClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,43 @@ public function testThrowExceptionInBodyGenerator()
$this->assertSame('bar ccc', $chunks[2]->getError());
}

public function testExceptionDirectlyInBody()
{
$mockHttpClient = new MockHttpClient([
new MockResponse(['foo', new \RuntimeException('foo ccc')]),
new MockResponse((static function (): \Generator {
yield 'bar';
yield new TransportException('bar ccc');
})()),
]);

try {
$mockHttpClient->request('GET', 'https://symfony.com', [])->getContent();
$this->fail();
} catch (TransportException $e) {
$this->assertEquals(new \RuntimeException('foo ccc'), $e->getPrevious());
$this->assertSame('foo ccc', $e->getMessage());
}

$chunks = [];
try {
foreach ($mockHttpClient->stream($mockHttpClient->request('GET', 'https://symfony.com', [])) as $chunk) {
$chunks[] = $chunk;
}
$this->fail();
} catch (TransportException $e) {
$this->assertEquals(new TransportException('bar ccc'), $e->getPrevious());
$this->assertSame('bar ccc', $e->getMessage());
}

$this->assertCount(3, $chunks);
$this->assertEquals(new FirstChunk(0, ''), $chunks[0]);
$this->assertEquals(new DataChunk(0, 'bar'), $chunks[1]);
$this->assertInstanceOf(ErrorChunk::class, $chunks[2]);
$this->assertSame(3, $chunks[2]->getOffset());
$this->assertSame('bar ccc', $chunks[2]->getError());
}

protected function getHttpClient(string $testCase): HttpClientInterface
{
$responses = [];
Expand Down