diff --git a/src/Symfony/Component/HttpClient/CHANGELOG.md b/src/Symfony/Component/HttpClient/CHANGELOG.md index 7c2fc2273b96a..56cbdf9924a8d 100644 --- a/src/Symfony/Component/HttpClient/CHANGELOG.md +++ b/src/Symfony/Component/HttpClient/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.1 +--- + + * Allow yielding `Exception` from MockResponse's `$body` to mock transport errors + 5.4 --- diff --git a/src/Symfony/Component/HttpClient/Response/MockResponse.php b/src/Symfony/Component/HttpClient/Response/MockResponse.php index 5dde67da27e7d..d143de2aa006b 100644 --- a/src/Symfony/Component/HttpClient/Response/MockResponse.php +++ b/src/Symfony/Component/HttpClient/Response/MockResponse.php @@ -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 $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" */ @@ -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'])); diff --git a/src/Symfony/Component/HttpClient/Tests/MockHttpClientTest.php b/src/Symfony/Component/HttpClient/Tests/MockHttpClientTest.php index a18caecc19c36..1b3e140dbdea3 100644 --- a/src/Symfony/Component/HttpClient/Tests/MockHttpClientTest.php +++ b/src/Symfony/Component/HttpClient/Tests/MockHttpClientTest.php @@ -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 = [];