skip to content

In a Laravel feature test, what does actingAs($user) do, and how does it differ from logging in through the login route?

level: middleimportance: must knowfreq 60%

answer

  1. setUser on the guard, no login flow
  2. second argument picks the guard
  3. that guard becomes the default
  4. actingAsGuest for anonymous requests
  5. assertAuthenticated, assertGuest

basics

~20 s

actingAs($user) puts the user straight onto a guard and makes it the default for the rest of the test, skipping credentials, session login and the Login event. Use it for what sits behind login; test login itself through the login route.

solid answer

~40 s

`actingAs($user, $guard = null)` is an alias of `be()`: it calls `setUser($user)` on the named guard (the default one if none) and `shouldUse($guard)`, so that guard becomes the default for the rest of the test. No password is checked, no session login happens and no `Login` event fires, although the session guard still fires `Authenticated`. Every request that follows sees `$request->user()` and passes `auth` middleware. It is therefore the right tool for "a signed-in customer can check out", and the wrong one for the login form itself: post credentials to the login route and use `assertAuthenticated()` or `assertAuthenticatedAs($user)`. `actingAsGuest()` forgets the user for a guest-path test, and `assertGuest()` checks nobody is signed in.

code

php · 29 lines
php
<?php

namespace Tests\Feature;

use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class CheckoutAccessTest extends TestCase
{
    use RefreshDatabase;

    public function test_guests_are_sent_to_login(): void
    {
        $this->get('/checkout')->assertRedirect('/login');
        $this->assertGuest();
    }

    public function test_customers_can_open_checkout(): void
    {
        $customer = User::factory()->create();

        $this->actingAs($customer)
            ->get('/checkout')
            ->assertOk();

        $this->assertAuthenticatedAs($customer);
    }
}

go deeper

for a junior

Use actingAs($user) before a request to test pages that need a signed-in user, and assertGuest or assertAuthenticated to check the state.

for a middle

Explain that actingAs calls setUser and shouldUse on the guard, skipping credentials, session login and the Login event, and how the guard argument works.

for a senior

Name the guard each route really uses, test login through its route, and avoid asserting side effects that actingAs never triggers.

for a principal

Decide how the suite covers authentication: one thorough set of login-flow tests, and actingAs everywhere else so authorization tests stay fast and focused.

## What the helper actually does `actingAs()` lives in the `InteractsWithAuthentication` concern of Laravel's base test case. Its source is short: 1. `actingAs($user, $guard)` calls `be($user, $guard)`. 2. `be()` resets the model's `wasRecentlyCreated` flag if it is set, so a factory-made user does not behave like a just-created record. 3. It calls `$this->app['auth']->guard($guard)->setUser($user)`, placing the user on the guard's in-memory state. 4. It calls `$this->app['auth']->shouldUse($guard)`, making that guard the **default** for the remainder of the test. Because the user sits on the guard object inside the application instance, every request made afterwards in the same test sees it: `auth()->user()`, `$request->user()`, the `auth` middleware and policies all treat the request as signed in. ## What it skips | Real login through the route | `actingAs($user)` | |---|---| | checks the password against the hash | no credential check | | runs login throttling and validation | nothing runs | | writes the user id to the session and regenerates it | no session login | | fires `Attempting` and `Login` events | neither fires; the session guard still fires `Authenticated` | | may set a remember-me cookie | no cookie | That is exactly why it is useful: a checkout test should not re-test the login form every time. It is also why it cannot prove anything about login itself. ## Choosing the guard The second argument names the guard from `config/auth.php`: `actingAs($user, 'web')`, or an API guard such as `actingAs($admin, 'admin')` in a multi-guard app. The docs point out that this guard becomes the default for the duration of the test, so code that calls `auth()->user()` without naming a guard sees the same user. How guards and providers are defined is an authentication topic; the testing point is to name the guard your route actually uses. Token-based packages such as Sanctum ship their own helper for tokens with abilities. ## Testing the other side - `actingAsGuest()` forgets the user on the guard, useful when a base class signs someone in by default. - `assertGuest()` checks no user is authenticated. - `assertAuthenticated()` checks someone is. - `assertAuthenticatedAs($user)` checks the right someone is, by identifier and class. A sneaker shop's checkout typically needs three tests: a guest is redirected to login, a signed-in customer can place an order, and another customer cannot view that order. Only the first uses no user. ## Testing login itself For the login form, drive the real flow: ```php $this->post('/login', ['email' => $user->email, 'password' => 'password']) ->assertRedirect('/dashboard'); $this->assertAuthenticatedAs($user); ``` Here credentials, throttling, session regeneration and events all run, so the test protects the actual behaviour. ## Reusing a signed-in customer Most checkout tests need a signed-in customer, so teams wrap the setup in one of two ways: - a helper on `Tests\TestCase`, such as `signInAsCustomer()`, that creates a user with a factory, calls `actingAs()` and returns the user; - a `setUp()` override in one test class that signs in a default customer, with `actingAsGuest()` in the few tests that need a guest. The helper is usually better: each test states that it needs a signed-in user instead of inheriting one silently, and a guest test cannot start running as a customer because someone added a line to `setUp()`. The guard holds the user object exactly as it was passed. If a test later changes that user's row directly in the database, the guard still has the old in-memory values, so refresh the model or call `actingAs()` again with an updated instance. ## Common mistakes - Using `actingAs()` and then asserting that a `Login` listener ran; nothing fired it. - Forgetting the guard name in a multi-guard app, so a route protected by `auth:admin` still redirects. - Creating the user with a factory but never persisting it, then hitting a route whose policy reloads the user from the database. - Making several requests with different users in one test; the docs recommend one request per test, and the guard keeps the last user set. Interviewers use this question to separate candidates who know that `actingAs` is a shortcut around authentication from those who think it logs the user in exactly as the browser would.

  • A test uses actingAs($admin) but a route guarded by auth:admin still redirects to login; why?
    Without a second argument, `actingAs()` sets the user on the default guard, usually `web`. The route checks the `admin` guard, which has no user. Call `actingAs($admin, 'admin')`, which sets the user on that guard and makes it the default for the rest of the test.
  • Why does a test asserting that a Login event listener ran fail after actingAs()?
    `actingAs()` only calls `setUser()` on the guard; it never runs the login flow, so no `Login` event is dispatched. The session guard fires `Authenticated` instead. To test login side effects, post credentials to the login route so the real `attempt()` path runs.

actingAs is a backstage pass handed to you by the organiser: you are inside the venue without queueing at the ticket gate, so it proves nothing about whether the gate works.

saying these in an interview costs you the question

  • actingAs() logs the user in exactly as the login form would, events included.
  • actingAs() checks the user's password before authenticating.
  • The guard passed to actingAs() applies only to the next request.
  • actingAs() is the right way to test the login form itself.
  • actingAs() needs the web session middleware to take effect.