Easier endpoint tests with Policy::fake
Testing endpoints can become time-consuming when we’re testing authorization logic alongside their behaviour. For endpoints gated by complex policies, we end up with a lot of setup to just to get to get the controller method to run. Things get really frustrating once you change your policies and break a bunch of unrelated tests too. This sucks. Your endpoint tests should answer the question"What does it do?" instead of "When can I use it?"
In his article A simpler approach to testing policies , Joel Clermont talked about mocking the Laravel Policy classes to help deal with these issues. I loved the idea immediately and started to implement it in my endpoint tests.
But I kept forgetting how to do it!
The AppPolicy
Class
To make this approach easier to remember, I introduced an AppPolicy
base class for my policies. It provides a static fake method that let's us setup these partial mocks with ease. Take a look!
<?php
namespace App\Policies;
use Mockery\MockInterface;
use function Pest\Laravel\partialMock;
class AppPolicy
{
public static function fake(array $abilities): void
{
partialMock(static::class, function (MockInterface $mock) use ($abilities) {
foreach ($abilities as $ability => $result) {
$mock->shouldReceive($ability)
->andReturn($result)
->atLeast()
->once();
}
$mock->shouldIgnoreMissing();
});
}
}
Extending AppPolicy
in Custom Policies
By extending AppPolicy
, custom policies can easily use the fake
method:
class VideoPolicy extends AppPolicy
{
public function view($user, $video)
{
return $user->subscribed();
}
}
This simplifies mocking policy behavior in tests without duplicating logic.
Faking Policy Behavior in Tests
The fake
method can mock specific policy methods during tests:
VideoPolicy::fake([
'view' => true,
]);
This ensures the view
method always returns true
in the test, and the test will fail if it’s not called, reinforcing proper authorization logic.
Example Test Cases
Forbidden Access Test
it('is forbidden if the user cannot view the video', function () {
VideoPolicy::fake(['view' => false]);
actingAs(User::factory()->create());
$video = Video::factory()->create();
$response = get(route('videos.show', $video));
$response->assertForbidden();
});
Allowed Access Test
it('is ok if the user can view the video', function () {
VideoPolicy::fake(['view' => true]);
actingAs(User::factory()->create());
$video = Video::factory()->create();
$response = get(route('videos.show', $video));
$response->assertOk();
});
Testing Abilities in Inertia Responses
With Inertia, you can also test that abilities are passed to the frontend:
it('sends abilities to the frontend', function () {
VideoPolicy::fake([
'view' => true,
'edit' => true,
'delete' => false,
]);
actingAs(User::factory()->create());
$video = Video::factory()->create();
$response = get(route('videos.show', $video));
$response->assertInertia(fn (AssertableInertia $page) => $page
->where('can.view', true)
->where('can.edit', true)
->where('can.delete', false)
);
});
Benefits
Using Policy::fake
makes testing endpoint authorization even simpler. It really frees you up to focus on what should be happening in your app.
Feel free to copy what I’ve posted and give it a try. Post improvement suggestions in the comments if you any!