Laravel has middlewares that can run before generating the response to the user. In some scenarios, you may need to create a custom middleware to perform a specific request validation, such as checking that a user is not blocked.

If your user table has a blocked boolean column, you may need a custom middleware to prevent users from entering specific parts of your app.

Your middleware may look something similar to this.

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

final readonly class NotBlockedMiddleware
{
    /**
     * Process an incoming request.
     */
    public function handle(Request $request, Closure $next)
    {
        if (auth()->user()?->blocked) {
            return redirect()->route('blocked');
        }

        return $next($request);
    }
}

This middleware prevents blocked users from entering the requested route by redirecting them to a blocked page that explains to users that their account is blocked and the reason.

To ensure this works as expected and in accordance with programming standards, we should also create a test to verify that this middleware works as expected.

For testing in Laravel, we use Pest, but you can also use PHPUnit if you prefer.

The following test ensures the middleware can pass if it’s a non-blocked user.

it('can pass middleware if non-blocked user', function (): void {
    $user = User::factory()->create(['blocked' => false]);
    $this->actingAs($user);

    $request = Request::create(route('dashboard'));
    $next = fn (Request $request): Response => response('success');

    $middleware = new NotBlockedMiddleware();
    $response = $middleware->handle($request, $next);

    expect($response->content())->toBe('success');
});

Next, we test whether the middleware can pass if the user is a guest. It's recommended to skip guest users in this type of middleware, as it should only perform a specific check following the separation of concerns principle. Therefore, if a route should also not be accessible by guest users, it's better to use our custom middleware in conjunction with the Authenticated middleware.

it('can pass if no user provided', function () {
    $request = Request::create(route('dashboard'));
    $next = fn (Request $request): Response => response('success');

    $middleware = new NotBlockedMiddleware();
    $response = $middleware->handle($request, $next);

    expect($response->content())->toBe('success');
});

Finally, we test that the middleware blocks the request if a blocked user attempts to make it.

it('cannot pass middleware if blocked user', function (): void {
    $user = User::factory()->create(['blocked' => true]);
    $this->actingAs($user);

    $request = Request::create(route('dashboard'));
    $next = fn (Request $request): Response => response('success');

    $middleware = new NotBlockedMiddleware();
    $response = $middleware->handle($request, $next);

    expect($response->getStatusCode())->toBe(302)
        ->and($response->headers->get('Location'))->toContain('/blocked');
});

Now we're sure the middleware works as expected, and no side cases can appear in the future.