diff --git a/composer.json b/composer.json index b4c39293d4f6..c9d8d71a2c02 100644 --- a/composer.json +++ b/composer.json @@ -106,7 +106,7 @@ "league/flysystem-sftp-v3": "^3.0", "mockery/mockery": "^1.6", "nyholm/psr7": "^1.2", - "orchestra/testbench-core": "^9.0.15", + "orchestra/testbench-core": "^9.1.4", "pda/pheanstalk": "^5.0", "phpstan/phpstan": "^1.4.7", "phpunit/phpunit": "^10.5|^11.0", diff --git a/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php b/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php index a5468e378830..7834476953e5 100644 --- a/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php +++ b/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php @@ -63,13 +63,6 @@ trait MakesHttpRequests */ protected $withCredentials = false; - /** - * The latest test response (if any). - * - * @var \Illuminate\Testing\TestResponse|null - */ - public static $latestResponse; - /** * Define additional headers to be sent with the request. * @@ -592,7 +585,7 @@ public function call($method, $uri, $parameters = [], $cookies = [], $files = [] $response = $this->followRedirects($response); } - return static::$latestResponse = $this->createTestResponse($response, $request); + return $this->createTestResponse($response, $request); } /** diff --git a/src/Illuminate/Foundation/Testing/TestCase.php b/src/Illuminate/Foundation/Testing/TestCase.php index 94ec4c71d5be..1d7a17df84ce 100644 --- a/src/Illuminate/Foundation/Testing/TestCase.php +++ b/src/Illuminate/Foundation/Testing/TestCase.php @@ -5,7 +5,6 @@ use Illuminate\Contracts\Console\Kernel; use Illuminate\Foundation\Application; use PHPUnit\Framework\TestCase as BaseTestCase; -use Throwable; abstract class TestCase extends BaseTestCase { @@ -42,8 +41,6 @@ public function createApplication() */ protected function setUp(): void { - static::$latestResponse = null; - $this->setUpTheTestEnvironment(); } @@ -57,20 +54,6 @@ protected function refreshApplication() $this->app = $this->createApplication(); } - /** - * {@inheritdoc} - */ - protected function transformException(Throwable $error): Throwable - { - $response = static::$latestResponse ?? null; - - if (! is_null($response)) { - $response->transformNotSuccessfulException($error); - } - - return $error; - } - /** * Clean up the testing environment before the next test. * @@ -90,8 +73,6 @@ protected function tearDown(): void */ public static function tearDownAfterClass(): void { - static::$latestResponse = null; - static::tearDownAfterClassUsingTestCase(); } } diff --git a/src/Illuminate/Testing/TestResponse.php b/src/Illuminate/Testing/TestResponse.php index 467d6103aec3..5272b17ccd83 100644 --- a/src/Illuminate/Testing/TestResponse.php +++ b/src/Illuminate/Testing/TestResponse.php @@ -9,7 +9,6 @@ use Illuminate\Cookie\CookieValuePrefix; use Illuminate\Database\Eloquent\Collection as EloquentCollection; use Illuminate\Database\Eloquent\Model; -use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Support\Arr; use Illuminate\Support\Carbon; @@ -20,12 +19,10 @@ use Illuminate\Support\Traits\Macroable; use Illuminate\Support\Traits\Tappable; use Illuminate\Support\ViewErrorBag; -use Illuminate\Testing\Assert as PHPUnit; use Illuminate\Testing\Constraints\SeeInOrder; use Illuminate\Testing\Fluent\AssertableJson; +use Illuminate\Testing\TestResponseAssert as PHPUnit; use LogicException; -use PHPUnit\Framework\ExpectationFailedException; -use ReflectionProperty; use Symfony\Component\HttpFoundation\Cookie; use Symfony\Component\HttpFoundation\StreamedJsonResponse; use Symfony\Component\HttpFoundation\StreamedResponse; @@ -100,7 +97,7 @@ public static function fromBaseResponse($response, $request = null) */ public function assertSuccessful() { - PHPUnit::assertTrue( + PHPUnit::withResponse($this)->assertTrue( $this->isSuccessful(), $this->statusMessageWithDetails('>=200, <300', $this->getStatusCode()) ); @@ -117,12 +114,12 @@ public function assertSuccessfulPrecognition() { $this->assertNoContent(); - PHPUnit::assertTrue( + PHPUnit::withResponse($this)->assertTrue( $this->headers->has('Precognition-Success'), 'Header [Precognition-Success] not present on response.' ); - PHPUnit::assertSame( + PHPUnit::withResponse($this)->assertSame( 'true', $this->headers->get('Precognition-Success'), 'The Precognition-Success header was found, but the value is not `true`.' @@ -138,7 +135,7 @@ public function assertSuccessfulPrecognition() */ public function assertServerError() { - PHPUnit::assertTrue( + PHPUnit::withResponse($this)->assertTrue( $this->isServerError(), $this->statusMessageWithDetails('>=500, < 600', $this->getStatusCode()) ); @@ -156,7 +153,7 @@ public function assertStatus($status) { $message = $this->statusMessageWithDetails($status, $actual = $this->getStatusCode()); - PHPUnit::assertSame($status, $actual, $message); + PHPUnit::withResponse($this)->assertSame($status, $actual, $message); return $this; } @@ -181,7 +178,7 @@ protected function statusMessageWithDetails($expected, $actual) */ public function assertRedirect($uri = null) { - PHPUnit::assertTrue( + PHPUnit::withResponse($this)->assertTrue( $this->isRedirect(), $this->statusMessageWithDetails('201, 301, 302, 303, 307, 308', $this->getStatusCode()), ); @@ -201,12 +198,12 @@ public function assertRedirect($uri = null) */ public function assertRedirectContains($uri) { - PHPUnit::assertTrue( + PHPUnit::withResponse($this)->assertTrue( $this->isRedirect(), $this->statusMessageWithDetails('201, 301, 302, 303, 307, 308', $this->getStatusCode()), ); - PHPUnit::assertTrue( + PHPUnit::withResponse($this)->assertTrue( Str::contains($this->headers->get('Location'), $uri), 'Redirect location ['.$this->headers->get('Location').'] does not contain ['.$uri.'].' ); @@ -224,7 +221,7 @@ public function assertRedirectToRoute($name, $parameters = []) { $uri = route($name, $parameters); - PHPUnit::assertTrue( + PHPUnit::withResponse($this)->assertTrue( $this->isRedirect(), $this->statusMessageWithDetails('201, 301, 302, 303, 307, 308', $this->getStatusCode()), ); @@ -247,14 +244,14 @@ public function assertRedirectToSignedRoute($name = null, $parameters = []) $uri = route($name, $parameters); } - PHPUnit::assertTrue( + PHPUnit::withResponse($this)->assertTrue( $this->isRedirect(), $this->statusMessageWithDetails('201, 301, 302, 303, 307, 308', $this->getStatusCode()), ); $request = Request::create($this->headers->get('Location')); - PHPUnit::assertTrue( + PHPUnit::withResponse($this)->assertTrue( $request->hasValidSignature(), 'The response is not a redirect to a signed route.' ); @@ -264,7 +261,7 @@ public function assertRedirectToSignedRoute($name = null, $parameters = []) 'expires' => null, ]), '?'); - PHPUnit::assertEquals( + PHPUnit::withResponse($this)->assertEquals( app('url')->to($uri), $expectedUri ); } @@ -281,14 +278,14 @@ public function assertRedirectToSignedRoute($name = null, $parameters = []) */ public function assertHeader($headerName, $value = null) { - PHPUnit::assertTrue( + PHPUnit::withResponse($this)->assertTrue( $this->headers->has($headerName), "Header [{$headerName}] not present on response." ); $actual = $this->headers->get($headerName); if (! is_null($value)) { - PHPUnit::assertEquals( + PHPUnit::withResponse($this)->assertEquals( $value, $this->headers->get($headerName), "Header [{$headerName}] was found, but value [{$actual}] does not match [{$value}]." ); @@ -305,7 +302,7 @@ public function assertHeader($headerName, $value = null) */ public function assertHeaderMissing($headerName) { - PHPUnit::assertFalse( + PHPUnit::withResponse($this)->assertFalse( $this->headers->has($headerName), "Unexpected header [{$headerName}] is present on response." ); @@ -320,7 +317,7 @@ public function assertHeaderMissing($headerName) */ public function assertLocation($uri) { - PHPUnit::assertEquals( + PHPUnit::withResponse($this)->assertEquals( app('url')->to($uri), app('url')->to($this->headers->get('Location', '')) ); @@ -338,7 +335,7 @@ public function assertDownload($filename = null) $contentDisposition = explode(';', $this->headers->get('content-disposition', '')); if (trim($contentDisposition[0]) !== 'attachment') { - PHPUnit::fail( + PHPUnit::withResponse($this)->fail( 'Response does not offer a file download.'.PHP_EOL. 'Disposition ['.trim($contentDisposition[0]).'] found in header, [attachment] expected.' ); @@ -347,7 +344,7 @@ public function assertDownload($filename = null) if (! is_null($filename)) { if (isset($contentDisposition[1]) && trim(explode('=', $contentDisposition[1])[0]) !== 'filename') { - PHPUnit::fail( + PHPUnit::withResponse($this)->fail( 'Unsupported Content-Disposition header provided.'.PHP_EOL. 'Disposition ['.trim(explode('=', $contentDisposition[1])[0]).'] found in header, [filename] expected.' ); @@ -356,9 +353,9 @@ public function assertDownload($filename = null) $message = "Expected file [{$filename}] is not present in Content-Disposition header."; if (! isset($contentDisposition[1])) { - PHPUnit::fail($message); + PHPUnit::withResponse($this)->fail($message); } else { - PHPUnit::assertSame( + PHPUnit::withResponse($this)->assertSame( $filename, isset(explode('=', $contentDisposition[1])[1]) ? trim(explode('=', $contentDisposition[1])[1], " \"'") @@ -369,7 +366,7 @@ public function assertDownload($filename = null) return $this; } } else { - PHPUnit::assertTrue(true); + PHPUnit::withResponse($this)->assertTrue(true); return $this; } @@ -400,7 +397,7 @@ public function assertPlainCookie($cookieName, $value = null) */ public function assertCookie($cookieName, $value = null, $encrypted = true, $unserialize = false) { - PHPUnit::assertNotNull( + PHPUnit::withResponse($this)->assertNotNull( $cookie = $this->getCookie($cookieName, $encrypted && ! is_null($value), $unserialize), "Cookie [{$cookieName}] not present on response." ); @@ -411,7 +408,7 @@ public function assertCookie($cookieName, $value = null, $encrypted = true, $uns $cookieValue = $cookie->getValue(); - PHPUnit::assertEquals( + PHPUnit::withResponse($this)->assertEquals( $value, $cookieValue, "Cookie [{$cookieName}] was found, but value [{$cookieValue}] does not match [{$value}]." ); @@ -427,14 +424,14 @@ public function assertCookie($cookieName, $value = null, $encrypted = true, $uns */ public function assertCookieExpired($cookieName) { - PHPUnit::assertNotNull( + PHPUnit::withResponse($this)->assertNotNull( $cookie = $this->getCookie($cookieName, false), "Cookie [{$cookieName}] not present on response." ); $expiresAt = Carbon::createFromTimestamp($cookie->getExpiresTime(), date_default_timezone_get()); - PHPUnit::assertTrue( + PHPUnit::withResponse($this)->assertTrue( $cookie->getExpiresTime() !== 0 && $expiresAt->lessThan(Carbon::now()), "Cookie [{$cookieName}] is not expired, it expires at [{$expiresAt}]." ); @@ -450,14 +447,14 @@ public function assertCookieExpired($cookieName) */ public function assertCookieNotExpired($cookieName) { - PHPUnit::assertNotNull( + PHPUnit::withResponse($this)->assertNotNull( $cookie = $this->getCookie($cookieName, false), "Cookie [{$cookieName}] not present on response." ); $expiresAt = Carbon::createFromTimestamp($cookie->getExpiresTime(), date_default_timezone_get()); - PHPUnit::assertTrue( + PHPUnit::withResponse($this)->assertTrue( $cookie->getExpiresTime() === 0 || $expiresAt->greaterThan(Carbon::now()), "Cookie [{$cookieName}] is expired, it expired at [{$expiresAt}]." ); @@ -473,7 +470,7 @@ public function assertCookieNotExpired($cookieName) */ public function assertCookieMissing($cookieName) { - PHPUnit::assertNull( + PHPUnit::withResponse($this)->assertNull( $this->getCookie($cookieName, false), "Cookie [{$cookieName}] is present on response." ); @@ -525,7 +522,7 @@ public function getCookie($cookieName, $decrypt = true, $unserialize = false) */ public function assertContent($value) { - PHPUnit::assertSame($value, $this->content()); + PHPUnit::withResponse($this)->assertSame($value, $this->content()); return $this; } @@ -538,7 +535,7 @@ public function assertContent($value) */ public function assertStreamedContent($value) { - PHPUnit::assertSame($value, $this->streamedContent()); + PHPUnit::withResponse($this)->assertSame($value, $this->streamedContent()); return $this; } @@ -568,7 +565,7 @@ public function assertSee($value, $escape = true) $values = $escape ? array_map('e', $value) : $value; foreach ($values as $value) { - PHPUnit::assertStringContainsString((string) $value, $this->getContent()); + PHPUnit::withResponse($this)->assertStringContainsString((string) $value, $this->getContent()); } return $this; @@ -585,7 +582,7 @@ public function assertSeeInOrder(array $values, $escape = true) { $values = $escape ? array_map('e', $values) : $values; - PHPUnit::assertThat($values, new SeeInOrder($this->getContent())); + PHPUnit::withResponse($this)->assertThat($values, new SeeInOrder($this->getContent())); return $this; } @@ -606,7 +603,7 @@ public function assertSeeText($value, $escape = true) $content = strip_tags($this->getContent()); foreach ($values as $value) { - PHPUnit::assertStringContainsString((string) $value, $content); + PHPUnit::withResponse($this)->assertStringContainsString((string) $value, $content); } return $this; @@ -623,7 +620,7 @@ public function assertSeeTextInOrder(array $values, $escape = true) { $values = $escape ? array_map('e', $values) : $values; - PHPUnit::assertThat($values, new SeeInOrder(strip_tags($this->getContent()))); + PHPUnit::withResponse($this)->assertThat($values, new SeeInOrder(strip_tags($this->getContent()))); return $this; } @@ -642,7 +639,7 @@ public function assertDontSee($value, $escape = true) $values = $escape ? array_map('e', $value) : $value; foreach ($values as $value) { - PHPUnit::assertStringNotContainsString((string) $value, $this->getContent()); + PHPUnit::withResponse($this)->assertStringNotContainsString((string) $value, $this->getContent()); } return $this; @@ -664,7 +661,7 @@ public function assertDontSeeText($value, $escape = true) $content = strip_tags($this->getContent()); foreach ($values as $value) { - PHPUnit::assertStringNotContainsString((string) $value, $content); + PHPUnit::withResponse($this)->assertStringNotContainsString((string) $value, $content); } return $this; @@ -842,7 +839,7 @@ public function assertJsonValidationErrors($errors, $responseKey = 'errors') { $errors = Arr::wrap($errors); - PHPUnit::assertNotEmpty($errors, 'No validation errors were provided.'); + PHPUnit::withResponse($this)->assertNotEmpty($errors, 'No validation errors were provided.'); $jsonErrors = Arr::get($this->json(), $responseKey) ?? []; @@ -873,7 +870,7 @@ public function assertJsonValidationErrors($errors, $responseKey = 'errors') } if ($errorMissing) { - PHPUnit::fail( + PHPUnit::withResponse($this)->fail( "Failed to find a validation error in the response for key and message: '$key' => '$expectedMessage'".PHP_EOL.PHP_EOL.$errorMessage ); } @@ -898,7 +895,7 @@ public function assertJsonValidationErrorFor($key, $responseKey = 'errors') PHP_EOL.PHP_EOL.json_encode($jsonErrors, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE).PHP_EOL : 'Response does not have JSON validation errors.'; - PHPUnit::assertArrayHasKey( + PHPUnit::withResponse($this)->assertArrayHasKey( $key, $jsonErrors, "Failed to find a validation error in the response for key: '{$key}'".PHP_EOL.PHP_EOL.$errorMessage @@ -917,7 +914,7 @@ public function assertJsonValidationErrorFor($key, $responseKey = 'errors') public function assertJsonMissingValidationErrors($keys = null, $responseKey = 'errors') { if ($this->getContent() === '') { - PHPUnit::assertTrue(true); + PHPUnit::withResponse($this)->assertTrue(true); return $this; } @@ -925,7 +922,7 @@ public function assertJsonMissingValidationErrors($keys = null, $responseKey = ' $json = $this->json(); if (! Arr::has($json, $responseKey)) { - PHPUnit::assertTrue(true); + PHPUnit::withResponse($this)->assertTrue(true); return $this; } @@ -933,14 +930,14 @@ public function assertJsonMissingValidationErrors($keys = null, $responseKey = ' $errors = Arr::get($json, $responseKey, []); if (is_null($keys) && count($errors) > 0) { - PHPUnit::fail( + PHPUnit::withResponse($this)->fail( 'Response has unexpected validation errors: '.PHP_EOL.PHP_EOL. json_encode($errors, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) ); } foreach (Arr::wrap($keys) as $key) { - PHPUnit::assertFalse( + PHPUnit::withResponse($this)->assertFalse( isset($errors[$key]), "Found unexpected validation error for key: '{$key}'" ); @@ -961,7 +958,7 @@ public function assertJsonIsArray($key = null) $encodedData = json_encode($data); - PHPUnit::assertTrue( + PHPUnit::withResponse($this)->assertTrue( is_array($data) && str_starts_with($encodedData, '[') && str_ends_with($encodedData, ']') @@ -982,7 +979,7 @@ public function assertJsonIsObject($key = null) $encodedData = json_encode($data); - PHPUnit::assertTrue( + PHPUnit::withResponse($this)->assertTrue( is_array($data) && str_starts_with($encodedData, '{') && str_ends_with($encodedData, '}') @@ -1008,7 +1005,7 @@ public function decodeResponseJson() if ($this->exception) { throw $this->exception; } else { - PHPUnit::fail('Invalid JSON was returned from the route.'); + PHPUnit::withResponse($this)->fail('Invalid JSON was returned from the route.'); } } @@ -1047,7 +1044,7 @@ public function assertViewIs($value) { $this->ensureResponseHasView(); - PHPUnit::assertEquals($value, $this->original->name()); + PHPUnit::withResponse($this)->assertEquals($value, $this->original->name()); return $this; } @@ -1068,20 +1065,20 @@ public function assertViewHas($key, $value = null) $this->ensureResponseHasView(); if (is_null($value)) { - PHPUnit::assertTrue(Arr::has($this->original->gatherData(), $key)); + PHPUnit::withResponse($this)->assertTrue(Arr::has($this->original->gatherData(), $key)); } elseif ($value instanceof Closure) { - PHPUnit::assertTrue($value(Arr::get($this->original->gatherData(), $key))); + PHPUnit::withResponse($this)->assertTrue($value(Arr::get($this->original->gatherData(), $key))); } elseif ($value instanceof Model) { - PHPUnit::assertTrue($value->is(Arr::get($this->original->gatherData(), $key))); + PHPUnit::withResponse($this)->assertTrue($value->is(Arr::get($this->original->gatherData(), $key))); } elseif ($value instanceof EloquentCollection) { $actual = Arr::get($this->original->gatherData(), $key); - PHPUnit::assertInstanceOf(EloquentCollection::class, $actual); - PHPUnit::assertSameSize($value, $actual); + PHPUnit::withResponse($this)->assertInstanceOf(EloquentCollection::class, $actual); + PHPUnit::withResponse($this)->assertSameSize($value, $actual); - $value->each(fn ($item, $index) => PHPUnit::assertTrue($actual->get($index)->is($item))); + $value->each(fn ($item, $index) => PHPUnit::withResponse($this)->assertTrue($actual->get($index)->is($item))); } else { - PHPUnit::assertEquals($value, Arr::get($this->original->gatherData(), $key)); + PHPUnit::withResponse($this)->assertEquals($value, Arr::get($this->original->gatherData(), $key)); } return $this; @@ -1129,7 +1126,7 @@ public function assertViewMissing($key) { $this->ensureResponseHasView(); - PHPUnit::assertFalse(Arr::has($this->original->gatherData(), $key)); + PHPUnit::withResponse($this)->assertFalse(Arr::has($this->original->gatherData(), $key)); return $this; } @@ -1142,7 +1139,7 @@ public function assertViewMissing($key) protected function ensureResponseHasView() { if (! $this->responseHasView()) { - return PHPUnit::fail('The response is not a view.'); + return PHPUnit::withResponse($this)->fail('The response is not a view.'); } return $this; @@ -1179,20 +1176,20 @@ public function assertValid($keys = null, $errorBag = 'default', $responseKey = } if (empty($errors)) { - PHPUnit::assertTrue(true); + PHPUnit::withResponse($this)->assertTrue(true); return $this; } if (is_null($keys) && count($errors) > 0) { - PHPUnit::fail( + PHPUnit::withResponse($this)->fail( 'Response has unexpected validation errors: '.PHP_EOL.PHP_EOL. json_encode($errors, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) ); } foreach (Arr::wrap($keys) as $key) { - PHPUnit::assertFalse( + PHPUnit::withResponse($this)->assertFalse( isset($errors[$key]), "Found unexpected validation error for key: '{$key}'" ); @@ -1227,7 +1224,7 @@ public function assertInvalid($errors = null, : 'Response does not have validation errors in the session.'; foreach (Arr::wrap($errors) as $key => $value) { - PHPUnit::assertArrayHasKey( + PHPUnit::withResponse($this)->assertArrayHasKey( $resolvedKey = (is_int($key)) ? $value : $key, $sessionErrors, "Failed to find a validation error in session for key: '{$resolvedKey}'".PHP_EOL.PHP_EOL.$errorMessage @@ -1246,7 +1243,7 @@ public function assertInvalid($errors = null, } if (! $hasError) { - PHPUnit::fail( + PHPUnit::withResponse($this)->fail( "Failed to find a validation error for key and message: '$key' => '$message'".PHP_EOL.PHP_EOL.$errorMessage ); } @@ -1271,14 +1268,14 @@ public function assertSessionHas($key, $value = null) } if (is_null($value)) { - PHPUnit::assertTrue( + PHPUnit::withResponse($this)->assertTrue( $this->session()->has($key), "Session is missing expected key [{$key}]." ); } elseif ($value instanceof Closure) { - PHPUnit::assertTrue($value($this->session()->get($key))); + PHPUnit::withResponse($this)->assertTrue($value($this->session()->get($key))); } else { - PHPUnit::assertEquals($value, $this->session()->get($key)); + PHPUnit::withResponse($this)->assertEquals($value, $this->session()->get($key)); } return $this; @@ -1325,14 +1322,14 @@ public function assertSessionHasInput($key, $value = null) } if (is_null($value)) { - PHPUnit::assertTrue( + PHPUnit::withResponse($this)->assertTrue( $this->session()->hasOldInput($key), "Session is missing expected key [{$key}]." ); } elseif ($value instanceof Closure) { - PHPUnit::assertTrue($value($this->session()->getOldInput($key))); + PHPUnit::withResponse($this)->assertTrue($value($this->session()->getOldInput($key))); } else { - PHPUnit::assertEquals($value, $this->session()->getOldInput($key)); + PHPUnit::withResponse($this)->assertEquals($value, $this->session()->getOldInput($key)); } return $this; @@ -1356,9 +1353,9 @@ public function assertSessionHasErrors($keys = [], $format = null, $errorBag = ' foreach ($keys as $key => $value) { if (is_int($key)) { - PHPUnit::assertTrue($errors->has($value), "Session missing error: $value"); + PHPUnit::withResponse($this)->assertTrue($errors->has($value), "Session missing error: $value"); } else { - PHPUnit::assertContains(is_bool($value) ? (string) $value : $value, $errors->get($key, $format)); + PHPUnit::withResponse($this)->assertContains(is_bool($value) ? (string) $value : $value, $errors->get($key, $format)); } } @@ -1382,7 +1379,7 @@ public function assertSessionDoesntHaveErrors($keys = [], $format = null, $error } if (is_null($this->session()->get('errors'))) { - PHPUnit::assertTrue(true); + PHPUnit::withResponse($this)->assertTrue(true); return $this; } @@ -1391,9 +1388,9 @@ public function assertSessionDoesntHaveErrors($keys = [], $format = null, $error foreach ($keys as $key => $value) { if (is_int($key)) { - PHPUnit::assertFalse($errors->has($value), "Session has unexpected error: $value"); + PHPUnit::withResponse($this)->assertFalse($errors->has($value), "Session has unexpected error: $value"); } else { - PHPUnit::assertNotContains($value, $errors->get($key, $format)); + PHPUnit::withResponse($this)->assertNotContains($value, $errors->get($key, $format)); } } @@ -1409,7 +1406,7 @@ public function assertSessionHasNoErrors() { $hasErrors = $this->session()->has('errors'); - PHPUnit::assertFalse( + PHPUnit::withResponse($this)->assertFalse( $hasErrors, 'Session has unexpected errors: '.PHP_EOL.PHP_EOL. json_encode((function () use ($hasErrors) { @@ -1458,7 +1455,7 @@ public function assertSessionMissing($key) $this->assertSessionMissing($value); } } else { - PHPUnit::assertFalse( + PHPUnit::withResponse($this)->assertFalse( $this->session()->has($key), "Session has unexpected key [{$key}]." ); @@ -1577,7 +1574,7 @@ public function streamedContent() if (! $this->baseResponse instanceof StreamedResponse && ! $this->baseResponse instanceof StreamedJsonResponse) { - PHPUnit::fail('The response is not a streamed response.'); + PHPUnit::withResponse($this)->fail('The response is not a streamed response.'); } ob_start(function (string $buffer): string { @@ -1606,114 +1603,6 @@ public function withExceptions(Collection $exceptions) return $this; } - /** - * This method is called when test method did not execute successfully. - * - * @param \Throwable $exception - * @return \Throwable - */ - public function transformNotSuccessfulException($exception) - { - if (! $exception instanceof ExpectationFailedException) { - return $exception; - } - - if ($lastException = $this->exceptions->last()) { - return $this->appendExceptionToException($lastException, $exception); - } - - if ($this->baseResponse instanceof RedirectResponse) { - $session = $this->baseResponse->getSession(); - - if (! is_null($session) && $session->has('errors')) { - return $this->appendErrorsToException($session->get('errors')->all(), $exception); - } - } - - if ($this->baseResponse->headers->get('Content-Type') === 'application/json') { - $testJson = new AssertableJsonString($this->getContent()); - - if (isset($testJson['errors'])) { - return $this->appendErrorsToException($testJson->json(), $exception, true); - } - } - - return $exception; - } - - /** - * Append an exception to the message of another exception. - * - * @param \Throwable $exceptionToAppend - * @param \Throwable $exception - * @return \Throwable - */ - protected function appendExceptionToException($exceptionToAppend, $exception) - { - $exceptionMessage = is_string($exceptionToAppend) ? $exceptionToAppend : $exceptionToAppend->getMessage(); - - $exceptionToAppend = (string) $exceptionToAppend; - - $message = <<<"EOF" - The following exception occurred during the last request: - - $exceptionToAppend - - ---------------------------------------------------------------------------------- - - $exceptionMessage - EOF; - - return $this->appendMessageToException($message, $exception); - } - - /** - * Append errors to an exception message. - * - * @param array $errors - * @param \Throwable $exception - * @param bool $json - * @return \Throwable - */ - protected function appendErrorsToException($errors, $exception, $json = false) - { - $errors = $json - ? json_encode($errors, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) - : implode(PHP_EOL, Arr::flatten($errors)); - - // JSON error messages may already contain the errors, so we shouldn't duplicate them... - if (str_contains($exception->getMessage(), $errors)) { - return $exception; - } - - $message = <<<"EOF" - The following errors occurred during the last request: - - $errors - EOF; - - return $this->appendMessageToException($message, $exception); - } - - /** - * Append a message to an exception. - * - * @param string $message - * @param \Throwable $exception - * @return \Throwable - */ - protected function appendMessageToException($message, $exception) - { - $property = new ReflectionProperty($exception, 'message'); - - $property->setValue( - $exception, - $exception->getMessage().PHP_EOL.PHP_EOL.$message.PHP_EOL - ); - - return $exception; - } - /** * Dynamically access base response parameters. * diff --git a/src/Illuminate/Testing/TestResponseAssert.php b/src/Illuminate/Testing/TestResponseAssert.php new file mode 100644 index 000000000000..433178537884 --- /dev/null +++ b/src/Illuminate/Testing/TestResponseAssert.php @@ -0,0 +1,164 @@ +injectResponseContext($e); + } + } + + /** + * Pass static method calls to the Assert class. + * + * @param string $method + * @param array $args + * @return mixed + */ + public static function __callStatic($name, $arguments) + { + return Assert::$name(...$arguments); + } + + /** + * Inject additional context from the response into the exception message. + * + * @param \ExpectationFailedException $exception + * @return \ExpectationFailedException + */ + protected function injectResponseContext($exception) + { + if ($lastException = $this->response->exceptions->last()) { + return $this->appendExceptionToException($lastException, $exception); + } + + if ($this->response->baseResponse instanceof RedirectResponse) { + $session = $this->response->baseResponse->getSession(); + + if (! is_null($session) && $session->has('errors')) { + return $this->appendErrorsToException($session->get('errors')->all(), $exception); + } + } + + if ($this->response->baseResponse->headers->get('Content-Type') === 'application/json') { + $testJson = new AssertableJsonString($this->response->getContent()); + + if (isset($testJson['errors'])) { + return $this->appendErrorsToException($testJson->json(), $exception, true); + } + } + + return $exception; + } + + /** + * Append an exception to the message of another exception. + * + * @param \Throwable $exceptionToAppend + * @param \Throwable $exception + * @return \Throwable + */ + protected function appendExceptionToException($exceptionToAppend, $exception) + { + $exceptionMessage = is_string($exceptionToAppend) ? $exceptionToAppend : $exceptionToAppend->getMessage(); + + $exceptionToAppend = (string) $exceptionToAppend; + + $message = <<<"EOF" + The following exception occurred during the last request: + + $exceptionToAppend + + ---------------------------------------------------------------------------------- + + $exceptionMessage + EOF; + + return $this->appendMessageToException($message, $exception); + } + + /** + * Append errors to an exception message. + * + * @param array $errors + * @param \Throwable $exception + * @param bool $json + * @return \Throwable + */ + protected function appendErrorsToException($errors, $exception, $json = false) + { + $errors = $json + ? json_encode($errors, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) + : implode(PHP_EOL, Arr::flatten($errors)); + + // JSON error messages may already contain the errors, so we shouldn't duplicate them... + if (str_contains($exception->getMessage(), $errors)) { + return $exception; + } + + $message = <<<"EOF" + The following errors occurred during the last request: + + $errors + EOF; + + return $this->appendMessageToException($message, $exception); + } + + /** + * Append a message to an exception. + * + * @param string $message + * @param \Throwable $exception + * @return \Throwable + */ + protected function appendMessageToException($message, $exception) + { + $property = new ReflectionProperty($exception, 'message'); + + $property->setValue( + $exception, + $exception->getMessage().PHP_EOL.PHP_EOL.$message.PHP_EOL + ); + + return $exception; + } +} diff --git a/tests/Foundation/Testing/TestCaseTest.php b/tests/Foundation/Testing/TestCaseTest.php deleted file mode 100644 index 76e16d75b35a..000000000000 --- a/tests/Foundation/Testing/TestCaseTest.php +++ /dev/null @@ -1,123 +0,0 @@ -withExceptions(collect([new Exception('Unexpected exception.')])); - - $this->expectException(ExpectationFailedException::class); - $this->expectExceptionMessageMatches('/Assertion message.*Unexpected exception/s'); - - $testCase::$latestResponse->transformNotSuccessfulException( - $exception = new ExpectationFailedException('Assertion message.'), - ); - - throw $exception; - } - - public function test_it_includes_validation_errors_on_test_failures() - { - $testCase = new ExampleTestCase('foo'); - $testCase::$latestResponse = TestResponse::fromBaseResponse( - tap(new RedirectResponse('/')) - ->setSession(new Store('test-session', new NullSessionHandler())) - ->withErrors([ - 'first_name' => 'The first name field is required.', - ]) - ); - - $this->expectException(ExpectationFailedException::class); - $this->expectExceptionMessageMatches('/Assertion message.*The first name field is required/s'); - - $testCase::$latestResponse->transformNotSuccessfulException( - $exception = new ExpectationFailedException('Assertion message.'), - ); - - throw $exception; - } - - public function test_it_includes_json_validation_errors_on_test_failures() - { - $testCase = new ExampleTestCase('foo'); - $testCase::$latestResponse = TestResponse::fromBaseResponse( - new Response(['errors' => ['first_name' => 'The first name field is required.']]) - ); - - $this->expectException(ExpectationFailedException::class); - $this->expectExceptionMessageMatches('/Assertion message.*The first name field is required/s'); - - $testCase::$latestResponse->transformNotSuccessfulException( - $exception = new ExpectationFailedException('Assertion message.'), - ); - - throw $exception; - } - - public function test_it_doesnt_fail_with_false_json() - { - $testCase = new ExampleTestCase('foo'); - $testCase::$latestResponse = TestResponse::fromBaseResponse( - new Response(false, 200, ['Content-Type' => 'application/json']) - ); - - $this->expectException(ExpectationFailedException::class); - $this->expectExceptionMessageMatches('/Assertion message/s'); - - $testCase::$latestResponse->transformNotSuccessfulException( - $exception = new ExpectationFailedException('Assertion message.'), - ); - - throw $exception; - } - - public function test_it_doesnt_fail_with_encoded_json() - { - $testCase = new ExampleTestCase('foo'); - $testCase::$latestResponse = TestResponse::fromBaseResponse( - tap(new Response, function ($response) { - $response->header('Content-Type', 'application/json'); - $response->header('Content-Encoding', 'gzip'); - $response->setContent('b"x£½V*.I,)-V▓R╩¤V¬\x05\x00+ü\x059"'); - }) - ); - - $this->expectException(ExpectationFailedException::class); - $this->expectExceptionMessageMatches('/Assertion message/s'); - - $testCase::$latestResponse->transformNotSuccessfulException( - $exception = new ExpectationFailedException('Assertion message.'), - ); - - throw $exception; - } - - protected function tearDown(): void - { - ExampleTestCase::$latestResponse = null; - - parent::tearDown(); - } -} - -class ExampleTestCase extends TestCase -{ - public function createApplication() - { - // - } -} diff --git a/tests/Testing/TestResponseTest.php b/tests/Testing/TestResponseTest.php index f4c1ee713a6b..d448f62f1d0a 100644 --- a/tests/Testing/TestResponseTest.php +++ b/tests/Testing/TestResponseTest.php @@ -2,6 +2,7 @@ namespace Illuminate\Tests\Testing; +use Exception; use Illuminate\Container\Container; use Illuminate\Contracts\View\View; use Illuminate\Cookie\CookieValuePrefix; @@ -9,12 +10,14 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Encryption\Encrypter; use Illuminate\Filesystem\Filesystem; +use Illuminate\Http\JsonResponse; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\Routing\RouteCollection; use Illuminate\Routing\UrlGenerator; use Illuminate\Session\ArraySessionHandler; +use Illuminate\Session\NullSessionHandler; use Illuminate\Session\Store; use Illuminate\Support\Collection; use Illuminate\Support\MessageBag; @@ -2460,6 +2463,73 @@ public function testGetEncryptedCookie() $this->assertEquals($cookieValue, $cookie->getValue()); } + public function testHandledExceptionIsIncludedInAssertionFailure() + { + $response = TestResponse::fromBaseResponse(new Response('', 500)) + ->withExceptions(collect([new Exception('Unexpected exception.')])); + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessageMatches('/Expected response status code \[200\] but received 500.*Exception: Unexpected exception/s'); + + $response->assertStatus(200); + } + + public function testValidationErrorsAreIncludedInAssertionFailure() + { + $response = TestResponse::fromBaseResponse( + tap(new RedirectResponse('/')) + ->setSession(new Store('test-session', new NullSessionHandler())) + ->withErrors([ + 'first_name' => 'The first name field is required.', + 'last_name' => 'The last name field is required.', + ]) + ); + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessageMatches('/Expected response status code \[200\] but received 302.*The first name field is required.*The last name field is required/s'); + + $response->assertStatus(200); + } + + public function testJsonErrorsAreIncludedInAssertionFailure() + { + $response = TestResponse::fromBaseResponse(new JsonResponse([ + 'errors' => [ + 'first_name' => ['The first name field is required.'], + 'last_name' => ['The last name field is required.'], + ], + ], 422)); + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessageMatches('/Expected response status code \[200\] but received 422.*The first name field is required.*The last name field is required/s'); + + $response->assertStatus(200); + } + + public function testItHandlesFalseJson() + { + $response = TestResponse::fromBaseResponse( + new Response(false, 422, ['Content-Type' => 'application/json']) + ); + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage('Expected response status code [200] but received 422.'); + + $response->assertStatus(200); + } + + public function testItHandlesEncodedJson() + { + $response = TestResponse::fromBaseResponse( + new Response('b"x£½V*.I,)-V▓R╩¤V¬\x05\x00+ü\x059"', 422, ['Content-Type' => 'application/json', 'Content-Encoding' => 'gzip']) + ); + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage('Expected response status code [200] but received 422.'); + + $response->assertStatus(200); + } + private function makeMockResponse($content) { $baseResponse = tap(new Response, function ($response) use ($content) {