Testing Your Laravel Middleware

While working on an application I wanted to test my middleware. When doing some investigation on this issue I didn't find any satisfying solutions, most involved manually creating a Symfony request object or mocking the request entirely.

To aid in the testing of my middleware I created a set of helper methods and an assertion (you can also find them in this gist), which I have added to my TestCase class:

<?php

namespace Tests;

use Illuminate\Foundation\Testing\TestResponse;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
use PHPUnit\Framework\Assert;

abstract class TestCase extends BaseTestCase
{
    use CreatesApplication;

    /**
     * Setup the test environment.
     *
     * @return void
     */
    protected function setUp()
    {
        parent::setUp();

        TestResponse::macro('assertMiddlewarePassed', function () {
            Assert::assertEquals('__passed__', $this->content());
        });
    }

    /**
     * Call the given middleware.
     *
     * @param  string|string[]  $middleware
     * @param  string  $method
     * @param  array  $data
     * @return \Illuminate\Foundation\Testing\TestResponse
     */
    protected function callMiddleware($middleware, $method = 'GET', array $data = [])
    {
        return $this->call(
            $method, $this->makeMiddlewareRoute($method, $middleware), $data
        );
    }

    /**
     * Call the given middleware using a JSON request.
     *
     * @param  string|string[]  $middleware
     * @param  string  $method
     * @param  array  $data
     * @return \Illuminate\Foundation\Testing\TestResponse
     */
    protected function callMiddlewareJson($middleware, $method = 'GET', array $data = [])
    {
        return $this->json(
            $method, $this->makeMiddlewareRoute($method, $middleware), $data
        );
    }

    /**
     * Make a dummy route with the given middleware applied.
     *
     * @param  string  $method
     * @param  string|string[]  $middleware
     * @return string
     */
    protected function makeMiddlewareRoute($method, $middleware)
    {
        $method = strtolower($method);

        return $this->app->make('router')->{$method}('/__middleware__', [
            'middleware' => $middleware, 
            function () {
                return '__passed__';
            }
        ])->uri();
    }
}

Example

To demonstrate the usage of these helpers, let's test a simple admin authentication middleware. The purpose of the middleware is to protect admin routes by only allowing admin users and redirecting away users and guests, or returning a forbidden response for JSON requests. The implementation is as follows:

<?php

namespace App\Http\Middleware;

class AdminAuthenticate
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, \Closure $next)
    {
        $user = $request->user();

        if (is_null($user) || ! $user->isAdmin()) {
            return $request->expectsJson()
                ? response()->json(['message' => 'Forbidden.'], 403)
                : redirect()->home();
        }

        return $next($request);
    }
}

Using the previously defined helper methods, we can now easily test this middleware class!

<?php

namespace Tests\Http\Middleware;

use Tests\Testcase;
use App\Http\Middleware\AdminAuthenticate;
use Illuminate\Foundation\Testing\RefreshDatabase;

class AdminAuthenticateTest extends TestCase
{
    use RefreshDatabase;

    /** @test */
    public function it_passes_for_admins()
    {
        $user = factory(User::class)->create(['is_admin' => true]);

        $response = $this->actingAs($user)->callMiddleware(AdminAuthenticate::class);

        $response->assertMiddlewarePassed();
    }

    /** @test */
    public function it_redirects_a_non_admin_user_to_the_homepage()
    {
        $user = factory(User::class)->create(['is_admin' => false]);

        $response = $this->actingAs($user)->callMiddleware(AdminAuthenticate::class);

        $response->assertRedirect(route('home'));
    }

    /** @test */
    public function it_redirects_guests_to_the_homepage()
    {   
        $response = $this->callMiddleware(AdminAuthenticate::class);

        $response->assertRedirect(route('home'));        
    }

    /** @test */
    public function it_returns_a_forbidden_json_response_for_non_admin_users()
    {
        $user = factory(User::class)->create(['is_admin' => false]);

        $response = $this->actingAs($user)->callMiddlewareJson(AdminAuthenticate::class);

        $response->assertStatus(403);
    }

    /** @test */
    public function it_returns_a_forbidden_json_response_for_guests()
    {   
        $response = $this->callMiddlewareJson(AdminAuthenticate::class);

        $response->assertStatus(403);        
    }
}

Conclusion

With these helper methods and assertion you can test your middleware in a readable way with a lot of flexibility. If you have any questions or middleware test cases that are not solved by these helpers, leave a comment below!