Summer Sale! All accounts are 50% off this week.

__gregory's avatar

Difference between actingAs($user) and Auth::login($user)?

Hello,

I'm wondering what's the difference between $this->actingAs($user) and Auth::login($user) in tests.

I want to test if Laravel shows a 404 page if a user is not allowed to see a resource. When I use Auth::login($user) it works:

    /**
     * @test
     */
    public function show_displays_404()
    {
        $tenant = Tenant::factory()->create();
        $tenant2 = Tenant::factory()->create();

        $report = Report::factory()->create(['tenant_id' => $tenant->id]);
        $user = User::factory()->create(['tenant_id' => $tenant2->id]);

        Auth::login($user);

        $response = $this->get(route('reports.show', $report));

        $response->assertNotFound();
    }

But when I use $this->actingAs($user) the test fails ("Response status code [200] is not a not found status code."), meaning the user can see the ressource he's no allowed to:

    /**
     * @test
     */
    public function show_displays_404()
    {
        $tenant = Tenant::factory()->create();
        $tenant2 = Tenant::factory()->create();

        $report = Report::factory()->create(['tenant_id' => $tenant->id]);
        $user = User::factory()->create(['tenant_id' => $tenant2->id]);

        $response = $this->actingAs($user)->get(route('reports.show', $report));

        $response->assertNotFound();
    }

So what's exactly the difference here? I expected $this->actingAs($user) to be working the same way as Auth::login($user).

Thanks!

0 likes
5 replies
bugsysha's avatar
bugsysha
Best Answer
Level 61

$this->actingAs() is just setting the user to current guard. Auth::login() is updating the session, remembering the user by setting the token and cookie, and it is firing the events and setting the user to the guard.

For tests you should only use $this->actingAs() or $this->be().

Also, you should not assert that the resource is not found, you should assert that it is forbidden to access that resource.

2 likes
geneowak's avatar

@bugsysha why shouldn't one use Auth::login($user) in tests when their app uses the login event to set things. Say for example you have an app that uses teams or multi-tenancy and you use the login event to set the current_team_id or the tenant_id in the session? Those are some cases where you need the login event fired for your tests to work...

Is there any other way to get that event to fire in the tests?

__gregory's avatar

$this->actingAs() is just setting the user to current guard. Auth::login() is updating the session, remembering the user by setting the token and cookie, and it is firing the events and setting the user to the guard.

Ok this makes sense now, because I have a Listener that adds data to the session when a user logs in. I have now changed a line in my test to this and it passes:

$response = $this->actingAs($user)
    ->withSession(['tenant_id' => $tenant2->id])
    ->get(route('reports.show', $report));

Also, you should not assert that the resource is not found, you should assert that it is forbidden to access that resource.

Could you please elaborate on this? I am using Laravels route model binding feature and what basically happens is that Laravel is throwing a ModelNotFoundException and rendering a view with a 404 status code. I don't have a lot of experience in writing tests so I am wondering what would be a better approach to test this kind of stuff?

Thanks!

bugsysha's avatar

Could you please elaborate on this?

Sure. There is nothing smart about it. If you use single database and that record exists then you found it but just do not allow the user to see it since that specific user is not the "owner" of that record. If you are using multiple databases and you try to access a record which does not exist in that database then it is OK to respond with not found.

From your code I would say that you have the first scenario and you should send forbidden response.

JerryBels's avatar

@bugsysha from the way his data is presented, I think he's using global scopes according to tenants. If so, it's not like when using policies or other authorization stuff, as the user can't see the row at all even if it's present in the DB, and Laravel will answer with a 404.

Please or to participate in this conversation.