From 9bf116882f29b4a094373f3f9d5147e5503150e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20H=C3=A4drich?= <11225821+shaedrich@users.noreply.github.com> Date: Thu, 2 Jul 2026 21:58:32 +0200 Subject: [PATCH 01/14] Add `callRoute()` to `MakesHttpRequests` trait --- .../Testing/Concerns/MakesHttpRequests.php | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php b/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php index 0e7b7d16319a..271d5c55e6df 100644 --- a/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php +++ b/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php @@ -581,6 +581,37 @@ public function json($method, $uri, array $data = [], array $headers = [], $opti ); } + public function callRoute(BackedEnum|string $name, array $parameters = [], array $cookies = [], array $files = [], array $server = [], ?string $content = null, ?string $method = null): TestResponse + { + if ($name instanceof BackedEnum && ! is_string($name = $name->value)) { + throw new InvalidArgumentException('Attribute [name] expects a string backed enum.'); + } + + if (is_null($route = app('router')->getRoutes()->getByName($name))) { + throw new RouteNotFoundException("Route [{$name}] not defined."); + } + + if (count($route->methods) > 1 && $method === null) { + throw new InvalidArgumentException('This route supports multiple HTTP methods. Please provide one of: ' implode(', ', $route->methods)); + } + + if ($method !== null && in_array($method, $route->methods)) { + throw new InvalidArgumentException("HTTP method [{$method}] not support by this route. Please provide one of: " implode(', ', $route->methods)); + } + + $method ??= $route->methods[0]; + + return $this->call( + $method, + $route->uri, + [], + $this->prepareCookiesForJsonRequest(), + $files, + $this->transformHeadersToServerVars($headers), + $content, + ); + } + /** * Call the given URI and return the Response. * From 6e44587d9f5432cdffa9813ddeab06e4e99434c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20H=C3=A4drich?= <11225821+shaedrich@users.noreply.github.com> Date: Thu, 2 Jul 2026 22:27:18 +0200 Subject: [PATCH 02/14] Add test for `MakesHttpRequests::callJson()` --- .../Concerns/MakesHttpRequestsTest.php | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/tests/Foundation/Testing/Concerns/MakesHttpRequestsTest.php b/tests/Foundation/Testing/Concerns/MakesHttpRequestsTest.php index 86340e27f347..7ba4e43a579b 100644 --- a/tests/Foundation/Testing/Concerns/MakesHttpRequestsTest.php +++ b/tests/Foundation/Testing/Concerns/MakesHttpRequestsTest.php @@ -7,6 +7,7 @@ use Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests; use Illuminate\Http\RedirectResponse; use Orchestra\Testbench\TestCase; +use PHPUnit\Framework\Attributes\DataProvider; class MakesHttpRequestsTest extends TestCase { @@ -239,6 +240,64 @@ public function testWithPrecognition() ->assertHeader('Precognition', 'true') ->assertHeader('Precognition-Success', 'true'); } + + #[DataProvider('providesCallRouteExceptions')] + public function testCallRouteFails(string $name, ?string $method, \Throwable $exception) + { + $this->expectExceptionObject($exception); + + $router = $this->app->make(Registrar::class); + $router->match(['PUT', 'PATCH'], '/', fn () => '')->name('test'); + $this->callRoute($name, method: $method); + } + + /** + * @return array + */ + public static function providesCallRouteExceptions(): array + { + return [ + 'integer-backed enum' => [RouteNumbers::Foo, null, new InvalidArgumentException('Attribute [name] expects a string backed enum.')], + 'non-existing route' => ['foo', null, new RouteNotFoundException("Route [{$name}] not defined.")], + 'ambigious HTTP method' => ['test', null, new InvalidArgumentException('This route supports multiple HTTP methods. Please provide one of: PUT, PATCH')], + 'wrong HTTP method' => ['test', 'DELETE', new InvalidArgumentException('HTTP method [DELETE] not support by this route. Please provide one of: PUT, PATCH')], + ]; + } + + #[DataProvider('providesCallRouteOptions')] + public function testCallRouteSucceeds(string $name, ?string $method) + { + $router = $this->app->make(Registrar::class); + $router->get(['/', fn () => 'tada!')->name('foo'); + $router->match(['PUT', 'PATCH'], '/', fn () => 'tada!')->name('bar'); + $response = $this->callRoute($name, method: $method); + $response->assertOk(); + $response->assertSeeText('tada!'); + } + + /** + * @return array + */ + public static function providesCallRouteOptions(): array + { + return [ + 'unambigious HTTP method' => [RouteNames::Foo, null], + 'provide HTTP method for ambigious route' => [RouteNames::Bar, 'PUT'], + 'provide HTTP method despite being unambigious' => ['foo', 'GET'], + ]; + } +} + +enum RouteNumbers: int +{ + case Foo = 1; + case Bar = 2; +} + +enum RouteNames: string +{ + case Foo = 'foo'; + case Bar = 'bar'; } class MyMiddleware From 29d88608b1b3eb203c7223282b4673a803254489 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20H=C3=A4drich?= <11225821+shaedrich@users.noreply.github.com> Date: Thu, 2 Jul 2026 22:38:24 +0200 Subject: [PATCH 03/14] Add missing dot --- .../Foundation/Testing/Concerns/MakesHttpRequests.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php b/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php index 271d5c55e6df..08f1d2e07ce8 100644 --- a/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php +++ b/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php @@ -592,11 +592,11 @@ public function callRoute(BackedEnum|string $name, array $parameters = [], array } if (count($route->methods) > 1 && $method === null) { - throw new InvalidArgumentException('This route supports multiple HTTP methods. Please provide one of: ' implode(', ', $route->methods)); + throw new InvalidArgumentException('This route supports multiple HTTP methods. Please provide one of: ' . implode(', ', $route->methods)); } if ($method !== null && in_array($method, $route->methods)) { - throw new InvalidArgumentException("HTTP method [{$method}] not support by this route. Please provide one of: " implode(', ', $route->methods)); + throw new InvalidArgumentException("HTTP method [{$method}] not support by this route. Please provide one of: " . implode(', ', $route->methods)); } $method ??= $route->methods[0]; From fb2516c1be574199f418f2a8780a42f4c2601ff1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20H=C3=A4drich?= <11225821+shaedrich@users.noreply.github.com> Date: Thu, 2 Jul 2026 22:39:11 +0200 Subject: [PATCH 04/14] Remove leftover square bracket --- tests/Foundation/Testing/Concerns/MakesHttpRequestsTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Foundation/Testing/Concerns/MakesHttpRequestsTest.php b/tests/Foundation/Testing/Concerns/MakesHttpRequestsTest.php index 7ba4e43a579b..2e51a67f3632 100644 --- a/tests/Foundation/Testing/Concerns/MakesHttpRequestsTest.php +++ b/tests/Foundation/Testing/Concerns/MakesHttpRequestsTest.php @@ -268,7 +268,7 @@ public static function providesCallRouteExceptions(): array public function testCallRouteSucceeds(string $name, ?string $method) { $router = $this->app->make(Registrar::class); - $router->get(['/', fn () => 'tada!')->name('foo'); + $router->get('/', fn () => 'tada!')->name('foo'); $router->match(['PUT', 'PATCH'], '/', fn () => 'tada!')->name('bar'); $response = $this->callRoute($name, method: $method); $response->assertOk(); From 806dc30f96967ac13fb46078e6e2b4a6bbe5ac53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20H=C3=A4drich?= <11225821+shaedrich@users.noreply.github.com> Date: Thu, 2 Jul 2026 22:40:30 +0200 Subject: [PATCH 05/14] StyleCI --- .../Foundation/Testing/Concerns/MakesHttpRequests.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php b/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php index 08f1d2e07ce8..8c52c8fe3ae5 100644 --- a/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php +++ b/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php @@ -600,7 +600,7 @@ public function callRoute(BackedEnum|string $name, array $parameters = [], array } $method ??= $route->methods[0]; - + return $this->call( $method, $route->uri, From bf91a5c9707b3f495b3740562bc4d17cdc6f55c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20H=C3=A4drich?= <11225821+shaedrich@users.noreply.github.com> Date: Thu, 2 Jul 2026 22:41:02 +0200 Subject: [PATCH 06/14] StyleCI --- .../Foundation/Testing/Concerns/MakesHttpRequests.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php b/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php index 8c52c8fe3ae5..c8bee745ad8f 100644 --- a/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php +++ b/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php @@ -592,11 +592,11 @@ public function callRoute(BackedEnum|string $name, array $parameters = [], array } if (count($route->methods) > 1 && $method === null) { - throw new InvalidArgumentException('This route supports multiple HTTP methods. Please provide one of: ' . implode(', ', $route->methods)); + throw new InvalidArgumentException('This route supports multiple HTTP methods. Please provide one of: '.implode(', ', $route->methods)); } if ($method !== null && in_array($method, $route->methods)) { - throw new InvalidArgumentException("HTTP method [{$method}] not support by this route. Please provide one of: " . implode(', ', $route->methods)); + throw new InvalidArgumentException("HTTP method [{$method}] not support by this route. Please provide one of: ".implode(', ', $route->methods)); } $method ??= $route->methods[0]; From a9549f8ec59b88d5fd3ae4202dc9d416c3511367 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20H=C3=A4drich?= <11225821+shaedrich@users.noreply.github.com> Date: Thu, 2 Jul 2026 22:44:12 +0200 Subject: [PATCH 07/14] Replace `$server` with higher level `$headers` --- .../Foundation/Testing/Concerns/MakesHttpRequests.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php b/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php index c8bee745ad8f..9cc125cf20a1 100644 --- a/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php +++ b/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php @@ -581,7 +581,7 @@ public function json($method, $uri, array $data = [], array $headers = [], $opti ); } - public function callRoute(BackedEnum|string $name, array $parameters = [], array $cookies = [], array $files = [], array $server = [], ?string $content = null, ?string $method = null): TestResponse + public function callRoute(BackedEnum|string $name, array $parameters = [], array $cookies = [], array $files = [], array $headers = [], ?string $content = null, ?string $method = null): TestResponse { if ($name instanceof BackedEnum && ! is_string($name = $name->value)) { throw new InvalidArgumentException('Attribute [name] expects a string backed enum.'); From c2c05cb6e34287cc9c27c1289053f1b28a7bd322 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20H=C3=A4drich?= <11225821+shaedrich@users.noreply.github.com> Date: Thu, 2 Jul 2026 22:45:08 +0200 Subject: [PATCH 08/14] Pass through everything we have --- .../Foundation/Testing/Concerns/MakesHttpRequests.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php b/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php index 9cc125cf20a1..072a5089b168 100644 --- a/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php +++ b/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php @@ -604,8 +604,8 @@ public function callRoute(BackedEnum|string $name, array $parameters = [], array return $this->call( $method, $route->uri, - [], - $this->prepareCookiesForJsonRequest(), + $parameters, + $cookies, $files, $this->transformHeadersToServerVars($headers), $content, From f06ea93239b945e2f1fe3ffea8dc23a0fdfab669 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20H=C3=A4drich?= <11225821+shaedrich@users.noreply.github.com> Date: Thu, 2 Jul 2026 22:49:12 +0200 Subject: [PATCH 09/14] Add `GET`/`HEAD` fallback --- .../Foundation/Testing/Concerns/MakesHttpRequests.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php b/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php index 072a5089b168..d7735db6b1df 100644 --- a/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php +++ b/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php @@ -591,6 +591,10 @@ public function callRoute(BackedEnum|string $name, array $parameters = [], array throw new RouteNotFoundException("Route [{$name}] not defined."); } + if (count($route->methods) === 2 && in_array('GET', $route->methods) && in_array('HEAD', $route->methods) && $method === null) { + $method = 'GET'; + } + if (count($route->methods) > 1 && $method === null) { throw new InvalidArgumentException('This route supports multiple HTTP methods. Please provide one of: '.implode(', ', $route->methods)); } From 3d07a2237b762cca7aca302f78756ce75d42196f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20H=C3=A4drich?= <11225821+shaedrich@users.noreply.github.com> Date: Thu, 2 Jul 2026 22:50:48 +0200 Subject: [PATCH 10/14] Add test case for `GET`/`HEAD` fallback --- tests/Foundation/Testing/Concerns/MakesHttpRequestsTest.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/Foundation/Testing/Concerns/MakesHttpRequestsTest.php b/tests/Foundation/Testing/Concerns/MakesHttpRequestsTest.php index 2e51a67f3632..3b7b1abb9a1a 100644 --- a/tests/Foundation/Testing/Concerns/MakesHttpRequestsTest.php +++ b/tests/Foundation/Testing/Concerns/MakesHttpRequestsTest.php @@ -270,6 +270,7 @@ public function testCallRouteSucceeds(string $name, ?string $method) $router = $this->app->make(Registrar::class); $router->get('/', fn () => 'tada!')->name('foo'); $router->match(['PUT', 'PATCH'], '/', fn () => 'tada!')->name('bar'); + $router->delete('/', fn () => 'tada!')->name('baz'); $response = $this->callRoute($name, method: $method); $response->assertOk(); $response->assertSeeText('tada!'); @@ -281,9 +282,10 @@ public function testCallRouteSucceeds(string $name, ?string $method) public static function providesCallRouteOptions(): array { return [ - 'unambigious HTTP method' => [RouteNames::Foo, null], + 'unambigious HTTP method' => ['baz', null], + 'GET/HEAD HTTP method fallback' => [RouteNames::Foo, null], 'provide HTTP method for ambigious route' => [RouteNames::Bar, 'PUT'], - 'provide HTTP method despite being unambigious' => ['foo', 'GET'], + 'provide HTTP method despite being unambigious' => ['baz', 'DELETE'], ]; } } From 620dde4b3f98cff42f253340993316596ebb6067 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20H=C3=A4drich?= <11225821+shaedrich@users.noreply.github.com> Date: Thu, 2 Jul 2026 22:58:04 +0200 Subject: [PATCH 11/14] Add missing imports --- tests/Foundation/Testing/Concerns/MakesHttpRequestsTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/Foundation/Testing/Concerns/MakesHttpRequestsTest.php b/tests/Foundation/Testing/Concerns/MakesHttpRequestsTest.php index 3b7b1abb9a1a..fa2f9052ea76 100644 --- a/tests/Foundation/Testing/Concerns/MakesHttpRequestsTest.php +++ b/tests/Foundation/Testing/Concerns/MakesHttpRequestsTest.php @@ -6,8 +6,10 @@ use Illuminate\Contracts\Routing\UrlGenerator; use Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests; use Illuminate\Http\RedirectResponse; +use InvalidArgumentException; use Orchestra\Testbench\TestCase; use PHPUnit\Framework\Attributes\DataProvider; +use Symfony\Component\Routing\Exception\RouteNotFoundException; class MakesHttpRequestsTest extends TestCase { From b435f6fafbd0abb420d76b33c3bd885ad801df81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20H=C3=A4drich?= <11225821+shaedrich@users.noreply.github.com> Date: Thu, 2 Jul 2026 22:58:43 +0200 Subject: [PATCH 12/14] Fix types --- tests/Foundation/Testing/Concerns/MakesHttpRequestsTest.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/Foundation/Testing/Concerns/MakesHttpRequestsTest.php b/tests/Foundation/Testing/Concerns/MakesHttpRequestsTest.php index fa2f9052ea76..c8d4eaec699c 100644 --- a/tests/Foundation/Testing/Concerns/MakesHttpRequestsTest.php +++ b/tests/Foundation/Testing/Concerns/MakesHttpRequestsTest.php @@ -10,6 +10,7 @@ use Orchestra\Testbench\TestCase; use PHPUnit\Framework\Attributes\DataProvider; use Symfony\Component\Routing\Exception\RouteNotFoundException; +use UnitEnum; class MakesHttpRequestsTest extends TestCase { @@ -244,7 +245,7 @@ public function testWithPrecognition() } #[DataProvider('providesCallRouteExceptions')] - public function testCallRouteFails(string $name, ?string $method, \Throwable $exception) + public function testCallRouteFails(string|UnitEnum $name, ?string $method, \Throwable $exception) { $this->expectExceptionObject($exception); @@ -267,7 +268,7 @@ public static function providesCallRouteExceptions(): array } #[DataProvider('providesCallRouteOptions')] - public function testCallRouteSucceeds(string $name, ?string $method) + public function testCallRouteSucceeds(string|UnitEnum $name, ?string $method) { $router = $this->app->make(Registrar::class); $router->get('/', fn () => 'tada!')->name('foo'); From 8ca9317a884c9d1c445212bc17ee2914c1b26b73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20H=C3=A4drich?= <11225821+shaedrich@users.noreply.github.com> Date: Thu, 2 Jul 2026 22:58:59 +0200 Subject: [PATCH 13/14] Add missing import --- src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php b/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php index d7735db6b1df..1f7c520bcc89 100644 --- a/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php +++ b/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php @@ -12,6 +12,7 @@ use Illuminate\Testing\TestResponse; use Symfony\Component\HttpFoundation\File\UploadedFile as SymfonyUploadedFile; use Symfony\Component\HttpFoundation\Request as SymfonyRequest; +use Symfony\Component\Routing\Exception\RouteNotFoundException; trait MakesHttpRequests { From 890ecd01bc647a0b43083ece9bd268c5bf6eb347 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20H=C3=A4drich?= <11225821+shaedrich@users.noreply.github.com> Date: Thu, 2 Jul 2026 23:22:00 +0200 Subject: [PATCH 14/14] Use router directly instead of registrar --- .../Testing/Concerns/MakesHttpRequestsTest.php | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/Foundation/Testing/Concerns/MakesHttpRequestsTest.php b/tests/Foundation/Testing/Concerns/MakesHttpRequestsTest.php index c8d4eaec699c..f0d3740ffab1 100644 --- a/tests/Foundation/Testing/Concerns/MakesHttpRequestsTest.php +++ b/tests/Foundation/Testing/Concerns/MakesHttpRequestsTest.php @@ -249,8 +249,7 @@ public function testCallRouteFails(string|UnitEnum $name, ?string $method, \Thro { $this->expectExceptionObject($exception); - $router = $this->app->make(Registrar::class); - $router->match(['PUT', 'PATCH'], '/', fn () => '')->name('test'); + $router = $this->app['router']->match(['PUT', 'PATCH'], '/', fn () => '')->name('test'); $this->callRoute($name, method: $method); } @@ -270,10 +269,9 @@ public static function providesCallRouteExceptions(): array #[DataProvider('providesCallRouteOptions')] public function testCallRouteSucceeds(string|UnitEnum $name, ?string $method) { - $router = $this->app->make(Registrar::class); - $router->get('/', fn () => 'tada!')->name('foo'); - $router->match(['PUT', 'PATCH'], '/', fn () => 'tada!')->name('bar'); - $router->delete('/', fn () => 'tada!')->name('baz'); + $this->app['router']->get('/', fn () => 'tada!')->name('foo'); + $this->app['router']->match(['PUT', 'PATCH'], '/', fn () => 'tada!')->name('bar'); + $this->app['router']->delete('/', fn () => 'tada!')->name('baz'); $response = $this->callRoute($name, method: $method); $response->assertOk(); $response->assertSeeText('tada!');