In a Laravel feature test, what does actingAs($user) do, and how does it differ from logging in through the login route?
answer
- setUser on the guard, no login flow
- second argument picks the guard
- that guard becomes the default
- actingAsGuest for anonymous requests
- assertAuthenticated, assertGuest
basics
~20 sactingAs($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
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
Use actingAs($user) before a request to test pages that need a signed-in user, and assertGuest or assertAuthenticated to check the state.
Explain that actingAs calls setUser and shouldUse on the guard, skipping credentials, session login and the Login event, and how the guard argument works.
Name the guard each route really uses, test login through its route, and avoid asserting side effects that actingAs never triggers.
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.