GoodM4ven's avatar

actingAs() twice and within the same test

Greetings,

When testing an authorization policy, I'd do actingAs() the first time with a non-authorized $user and assert forbidden and then (within the same test) I'd do assert successful after acting as an authorized one.

But then an exception is thrown when the 2nd user is passed to actingAs() as it gets turned into null somehow while it's expecting an Authenticable instance!

I want to know who's the exact culprit here.

Note: I've seen this happen with Freek in his Testing Laravel course, "testing the manage blog post policy" video, where he'd promised that he'd debug this later and forgot or something!

0 likes
4 replies
LaryAI's avatar
Level 58

The issue here is that the second call to actingAs() is overriding the first one, which is causing the error. To solve this, you can use the asUser() method instead of actingAs() for the second user. Here's an example:

public function testPolicy()
{
    $user = factory(User::class)->create();
    $unauthorizedUser = factory(User::class)->create();

    $post = factory(Post::class)->create(['user_id' => $user->id]);

    $this->actingAs($unauthorizedUser);

    $this->assertFalse($user->can('update', $post));

    $this->asUser($user);

    $this->assertTrue($user->can('update', $post));
}

protected function asUser($user)
{
    $token = JWTAuth::fromUser($user);
    $this->withHeader('Authorization', 'Bearer ' . $token);
}

In this example, we're using asUser() to set the authorization header for the second user instead of calling actingAs() again. This allows us to test both the authorized and unauthorized scenarios within the same test.

martinbean's avatar
Level 80

@goodm4ven You should have different test cases, since you’re testing two different use cases.

You’ll face problems as requests in a Laravel test aren’t “real” requests, and instead just passed through a booted instance of the framework. The framework won’t be re-booted for multiple requests in the same test case, leading to things not being cleaned up and not working as expected, as you’ve found.

Instead, create a test case for the authenticated scenario, and another test case for the guest scenario:

public function testGuestCannotDoSomething(): void
{
    // TODO: Test guest cannot do something
}

public function testAuthenticatedUserCanDoSomething(): void
{
    // TODO: Test authenticated user can do something
}
Rikaelus's avatar

@martinbean Unless the thing you're testing is that there's no authorization leakage between multiple accounts. It helps drive home the test to log in as each within the same test and transaction to test their authorization rights individually.

martinbean's avatar

@Rikaelus The framework isn’t rebooted during “requests” within a single test case, so you therefore get state leakage and undesired behaviour that wouldn’t occur normally.

Please or to participate in this conversation.