Mocking with the container — $this->mock(), $this->spy()
Concept
Testing authentication and authorization verifies that routes are properly protected, roles are enforced, and unauthorized actions are rejected.
actingAs($user): Set the authenticated user for the request. No actual login flow — just sets the guard. $this->actingAs($user, 'api') for a specific guard.
be($user): Alias for actingAs().
assertAuthenticated(): Assert the current user is authenticated (after a login action). assertAuthenticatedAs($user).
assertGuest(): Assert not authenticated.
Testing login flow: Make a POST to the login route with credentials. Assert the user ends up authenticated and redirected.
Testing authorization:
- Routes protected by
authmiddleware should return 401 (or redirect to login for web) when no user. - Routes protected by a Gate or Policy should return 403 when authenticated but unauthorized.
->assertForbidden()checks for 403.
Gate::define() in test: You can override gates in a specific test: Gate::define('edit-post', fn($user) => $user->isAdmin()).
User factory with roles: User::factory()->admin()->create() if you have an admin state.
withoutMiddleware(): Bypass specific middleware (e.g., throttle or CSRF) in tests. Not for auth middleware — test auth properly.
Code Example
<?php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
use App\Models\User;
use App\Models\Post;
class AuthorizationTest extends TestCase
{
use RefreshDatabase;
// Auth middleware protection
public function test_guests_cannot_access_dashboard(): void
{
$this->get('/dashboard')->assertRedirect('/login');
$this->getJson('/api/user')->assertUnauthorized(); // 401
}
// Authenticated, wrong role
public function test_regular_user_cannot_access_admin(): void
{
$user = User::factory()->create(['role' => 'user']);
$this->actingAs($user)->get('/admin')->assertForbidden(); // 403
}
// Admin can access
public function test_admin_can_access_admin_panel(): void
{
$admin = User::factory()->admin()->create();
$this->actingAs($admin)->get('/admin')->assertOk();
}
// Policy: user can edit own post
public function test_user_can_edit_own_post(): void
{
$user = User::factory()->create();
$post = Post::factory()->for($user)->create();
$this->actingAs($user)
->patch("/posts/{$post->id}", ['title' => 'New Title', 'body' => 'body'])
->assertRedirect();
}
// Policy: user cannot edit other's post
public function test_user_cannot_edit_others_post(): void
{
$author = User::factory()->create();
$other = User::factory()->create();
$post = Post::factory()->for($author)->create();
$this->actingAs($other)
->patch("/posts/{$post->id}", ['title' => 'Hack', 'body' => 'body'])
->assertForbidden();
}
// Login flow
public function test_user_can_login_with_correct_credentials(): void
{
$user = User::factory()->create(['password' => bcrypt('secret')]);
$this->post('/login', ['email' => $user->email, 'password' => 'secret'])
->assertRedirect('/dashboard');
$this->assertAuthenticatedAs($user);
}
public function test_invalid_credentials_rejected(): void
{
$user = User::factory()->create(['password' => bcrypt('secret')]);
$this->post('/login', ['email' => $user->email, 'password' => 'wrong'])
->assertSessionHasErrors(['email']);
$this->assertGuest();
}
}