From cc52e7f866ed03d79ec238adb6481bdae0377f4f Mon Sep 17 00:00:00 2001 From: soyuka Date: Thu, 4 Jun 2026 10:51:43 +0200 Subject: [PATCH 1/2] fix(test): capture streamedresponse body in test client response Since Symfony 6.1 (symfony/symfony#46445), HttpFoundation's StreamedResponse::getContent() returns false because the body is streamed via a callback and never buffered on the response object. Casting that to string in the test client's Response wrapper yielded an empty body, so assertions on streamed endpoints (e.g. a state provider returning StreamedResponse or StreamedJsonResponse) read "" instead of the actual content. BrowserKit's HttpKernelBrowser::filterResponse already buffers the streamed output via ob_start() and stores it on the BrowserKit Response. The test client wrapper now reads from the BrowserKit response when the kernel response is a StreamedResponse (covers StreamedJsonResponse too), avoiding both the empty-body case and any re-streaming. Fixes #5780 --- src/Symfony/Bundle/Test/Response.php | 8 ++++- tests/Symfony/Bundle/Test/ResponseTest.php | 34 ++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Bundle/Test/Response.php b/src/Symfony/Bundle/Test/Response.php index 3ac943beedf..94a0629ef70 100644 --- a/src/Symfony/Bundle/Test/Response.php +++ b/src/Symfony/Bundle/Test/Response.php @@ -20,6 +20,7 @@ use Symfony\Component\HttpClient\Exception\ServerException; use Symfony\Component\HttpClient\Exception\TransportException; use Symfony\Component\HttpFoundation\Response as HttpFoundationResponse; +use Symfony\Component\HttpFoundation\StreamedResponse; use Symfony\Contracts\HttpClient\ResponseInterface; /** @@ -50,7 +51,12 @@ public function __construct(private readonly HttpFoundationResponse $httpFoundat } } - $this->content = (string) $httpFoundationResponse->getContent(); + // StreamedResponse::getContent() returns false because the body is streamed via a callback. + // BrowserKit's HttpKernelBrowser::filterResponse already buffered the streamed output into + // the BrowserKit response, so use it as the source of truth to avoid re-streaming. + $this->content = $httpFoundationResponse instanceof StreamedResponse + ? $browserKitResponse->getContent() + : (string) $httpFoundationResponse->getContent(); $this->info = [ 'http_code' => $httpFoundationResponse->getStatusCode(), 'error' => null, diff --git a/tests/Symfony/Bundle/Test/ResponseTest.php b/tests/Symfony/Bundle/Test/ResponseTest.php index 4d5c674420f..b8b3276e41f 100644 --- a/tests/Symfony/Bundle/Test/ResponseTest.php +++ b/tests/Symfony/Bundle/Test/ResponseTest.php @@ -23,6 +23,8 @@ use Symfony\Component\HttpClient\Exception\ServerException; use Symfony\Component\HttpClient\Exception\TransportException; use Symfony\Component\HttpFoundation\Response as HttpFoundationResponse; +use Symfony\Component\HttpFoundation\StreamedJsonResponse; +use Symfony\Component\HttpFoundation\StreamedResponse; class ResponseTest extends TestCase { @@ -122,4 +124,36 @@ public function testCancel(): void $this->assertSame('Response has been canceled.', $response->getInfo('error')); } + + public function testStreamedResponseContentIsCapturedFromBrowserKitResponse(): void + { + $streamedBody = '{"foo":"bar"}'; + // BrowserKit's HttpKernelBrowser::filterResponse already buffered the streamed body + // into the BrowserKit Response, but the HttpFoundation StreamedResponse::getContent() + // still returns false. The Response wrapper must read from the BrowserKit response. + $browserKitResponse = new BrowserKitResponse($streamedBody, 200, ['content-type' => 'application/json']); + $httpFoundationResponse = new StreamedResponse(static function () use ($streamedBody): void { + echo $streamedBody; + }, 200, ['content-type' => 'application/json']); + + $response = new Response($httpFoundationResponse, $browserKitResponse, []); + + $this->assertSame($streamedBody, $response->getContent()); + $this->assertSame(['foo' => 'bar'], $response->toArray()); + } + + public function testStreamedJsonResponseContentIsCapturedFromBrowserKitResponse(): void + { + if (!class_exists(StreamedJsonResponse::class)) { + $this->markTestSkipped('StreamedJsonResponse is not available.'); + } + + $streamedBody = '{"items":[1,2,3]}'; + $browserKitResponse = new BrowserKitResponse($streamedBody, 200, ['content-type' => 'application/json']); + $httpFoundationResponse = new StreamedJsonResponse(['items' => [1, 2, 3]]); + + $response = new Response($httpFoundationResponse, $browserKitResponse, []); + + $this->assertSame($streamedBody, $response->getContent()); + } } From f8a9a7ed5d9436f0d3b772525f4f1adc5f5f582c Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Thu, 4 Jun 2026 14:24:12 +0200 Subject: [PATCH 2/2] Update src/Symfony/Bundle/Test/Response.php --- src/Symfony/Bundle/Test/Response.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Symfony/Bundle/Test/Response.php b/src/Symfony/Bundle/Test/Response.php index 94a0629ef70..41a1afe82f6 100644 --- a/src/Symfony/Bundle/Test/Response.php +++ b/src/Symfony/Bundle/Test/Response.php @@ -51,9 +51,6 @@ public function __construct(private readonly HttpFoundationResponse $httpFoundat } } - // StreamedResponse::getContent() returns false because the body is streamed via a callback. - // BrowserKit's HttpKernelBrowser::filterResponse already buffered the streamed output into - // the BrowserKit response, so use it as the source of truth to avoid re-streaming. $this->content = $httpFoundationResponse instanceof StreamedResponse ? $browserKitResponse->getContent() : (string) $httpFoundationResponse->getContent();