Actors & HTTP¶
Journeys can drive the app over HTTP with named, authenticated actors, so a trail exercises your real routes, middleware, and controllers rather than calling services directly.
Step::make('sign up the voters')
->act(function (Context $ctx): void {
foreach (['ana', 'ben', 'cai'] as $name) {
$ctx->actingAs(User::query()->create(['name' => $name]), $name);
}
}),
Step::make('cast vote')
->after('publish post')
->repeatable()
->act(fn (Context $ctx) => $ctx->as($ctx->pick(['ana', 'ben', 'cai']))
->postJson(sprintf('/posts/%d/vote', $ctx->integer('post id')), ['value' => $ctx->pick([1, -1])]))
->assertWhen(
fn (Context $ctx): bool => Post::query()->findOrFail($ctx->integer('post id'))->status === 'locked',
fn (Context $ctx) => $ctx->lastResponse()->assertStatus(409),
fn (Context $ctx) => $ctx->lastResponse()->assertCreated(),
),
Registering and switching actors¶
$ctx->actingAs($user, 'name', $session = [])— register a named actor authenticated as$user, optionally with session state. Returns theActor.$ctx->as('name')— get a previously registered actor.$ctx->lastResponse()— the most recentTestResponsereturned by any actor.
Every request made through $ctx->as('name') is authenticated as that actor's user, so a journey can hop freely between participants. Actors proxy the full set of test HTTP verbs, each returning a TestResponse:
get, getJson, post, postJson, put, putJson, patch, patchJson, delete, deleteJson.
Declaring fixed participants with actors()¶
When the participants are fixed for the whole run, declare them once with actors() instead of a sign-up step — Runabout registers them on every trail's context for you (actors live on the per-trail context, so they otherwise have to be re-registered each trail):
final class ReviewJourney extends Journey
{
public function __construct(private User $manager, private User $agent) {}
public function actors(): array
{
return ['manager' => $this->manager, 'agent' => $this->agent];
}
// steps() can call $ctx->as('manager') / $ctx->as('agent') straight away.
}
The users declared in actors() must exist before the run (create them in the test and pass them in). Anything created inside a trail is rolled back before the next one, so register those with $ctx->actingAs() in a step — as the voters above do.
Carrying session / tenancy state¶
If your app carries tenancy (or anything else) in the session, attach it to the actor and it rides along with every request that actor makes — driving the full middleware stack, no per-request session juggling:
$ctx->actingAs($user, 'agent', ['current-tenant-id' => $tenant->id]); // persists for all of this actor's requests
$ctx->as('agent')->withSession(['impersonating' => 42])->postJson(...); // layered on for one request
withSession() returns a copy of the actor with the extra session data merged, for one request, leaving the actor's baseline session intact.
For state that lives outside the auth guard and session — and especially in interleaved journeys, where whose turn it is changes step to step — see the aroundStep() hook.