Testing Laravel routes have middleware
Matthew Erwin • November 9, 2020
laravel testingIn some applications you might require every route to be protected by authentication. We can use tests to make sure that all of your routes are using the correct middleware and are not accessible without the correct auth or permission.
Why you should test routes have middleware
You may have tests to check that that middleware works but what if you forget to add it to a new route? As well as making sure all routes are protected, writing a test for your routes also serves as documentation for developers who are unfamiliar with the application.
Basic test
// routes/web.php
Route::get('admin')->middleware('auth')->name('admin.dashboard.show');
Route::get('admin/posts')->middleware('auth')->name('admin.post.index');
Route::get('admin/settings')->name('admin.setting.index'); // Forgotten to add middleware
Route::get('/')->name('home');
Route::get('posts')->name('post.index');
// tests/Feature/AdminMiddlewareTest.php
namespace Tests\Feature;
use Illuminate\Routing\Route;
use Illuminate\Routing\Router;
use Illuminate\Support\Collection;
use Tests\TestCase;
class AdminMiddlewareTest extends TestCase
{
public function testAllAdminRoutesHaveAuthMiddleware()
{
/** @var Router $router */
$router = app('router');
/** @var Collection|Route[] $routes */
$unprotected_routes = collect($router->getRoutes())->filter(function (Route $route) {
if (!preg_match('/^admin/', $route->getName())) {
return false;
}
return !in_array('auth', $route->gatherMiddleware());
});
$this->assertCount(0, $unprotected_routes);
}
}
Making exceptions for some routes
Sometimes you may have a few routes which break the normal rules and are allowed to be accessed without authentication, your login page for example. We can easily make a list of excluded patterns which we will ignore in our test.
// config/auth.php
return [
'ignored_routes' => [
'/example\.index/',
],
];
// tests/Feature/AdminMiddlewareTest.php
namespace Tests\Feature;
use Illuminate\Routing\Route;
use Illuminate\Routing\Router;
use Illuminate\Support\Collection;
use Tests\TestCase;
class AdminMiddlewareTest extends TestCase
{
public function testAllAdminRoutesHaveAuthMiddleware()
{
/** @var Router $router */
$router = app('router');
/** @var Collection|Route[] $routes */
$unprotected_routes = collect($router->getRoutes())->filter(function (Route $route) {
if (!preg_match('/^admin/', $route->getName())) {
return false;
}
foreach (config('auth.ignored_routes') as $pattern) {
if (preg_match($pattern, $route->getName())) {
return false;
}
}
return !in_array('auth', $route->gatherMiddleware());
});
$this->assertCount(0, $unprotected_routes);
}
}
Returning a better error
So far we can see that there are routes missing middleware but we can't see which. We can use the $message
parameter in PHPUnit to pass a custom message.
// tests/Feature/AdminMiddlewareTest.php
namespace Tests\Feature;
use Illuminate\Routing\Route;
use Illuminate\Routing\Router;
use Illuminate\Support\Collection;
use Tests\TestCase;
class AdminMiddlewareTest extends TestCase
{
public function testAllAdminRoutesHaveAuthMiddleware()
{
/** @var Router $router */
$router = app('router');
/** @var Collection|Route[] $routes */
$unprotected_routes = collect($router->getRoutes())->filter(function (Route $route) {
if (!preg_match('/^admin/', $route->getName())) {
return false;
}
return !in_array('auth', $route->gatherMiddleware());
});
$message = $unprotected_routes->map(function(Route $route){
return sprintf('Route `%s` (%s) is missing `auth` middleware', $route->uri(), $route->getName());
})->implode("\n");
$this->assertCount(0, $unprotected_routes, $message);
}
}