Testing Laravel routes have middleware

Matthew Erwin • November 9, 2020

laravel testing

In 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);
    }
}

PHPUnit Test Results

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);
    }
}

PHPUnit Test Results