actingAs() has signed the team member in. The happy path is the request that member is allowed to make, with input the rules accept. Assert the response the browser would follow, then assert the row that proves the invoice exists.
Test-driven Laravel · Lesson 2
HTTP tests for happy paths
Video
01 Follow the redirect the controller returns
assertRedirect() without a URL only checks that the status is a redirect. Pass the path when the destination is part of the behaviour.
it('shows the invoice after creating it', function () {
$user = User::factory()->create();
$this->actingAs($user)
->post('/invoices', [
'number' => 'INV-1042',
'issued_on' => '2026-10-22',
])
->assertRedirect('/invoices/1');
$this->assertDatabaseHas('invoices', [
'number' => 'INV-1042',
'status' => 'draft',
'team_id' => $user->current_team_id,
]);
});Assert the columns the test is about. A draft status matters here because the action promised a draft. Asserting every timestamp only makes the test fail when the clock moves, which is Freezing time.
Hard-coding /invoices/1 is fair when RefreshDatabase means this is the first invoice. If a factory created one during arrange, follow the model’s id instead of guessing.
02 Follow the id the arrange already created
When the test builds a team first, the next invoice is not guaranteed to be id 1. Read the id from the redirect, or from the row you just asserted. assertRedirect() can take the path built from that id once you know it.
it('redirects to the invoice it stored', function () {
$user = User::factory()->create();
$this->actingAs($user)
->post('/invoices', [
'number' => 'INV-1042',
'issued_on' => '2026-10-22',
])
->assertRedirect();
$invoice = Invoice::query()->where('number', 'INV-1042')->firstOrFail();
$this->get('/invoices/'.$invoice->id)
->assertOk()
->assertSee('INV-1042');
});assertSee() checks the page a person would open. It does not replace the database assertion. A page can render the number from the session while the insert failed. Keep both when both are the behaviour.
assertRedirect('/invoices/'.$invoice->id) is the tighter form once the id is known. The looser assertRedirect() only checks the status. Use the tighter form when the destination is part of what the person was promised. A redirect to the index would still satisfy the loose assertion, and the invoice page would never have been checked.
Recap
You can assert the redirect a browser would follow and the row that proves the invoice exists.
assertRedirect()without a path only checks that it was a redirect.- Hard-coding
/invoices/1is fair only when this test created the first row. - Assert the columns the action promised. Skip timestamps.
Check yourself · 3 questions
Question 1 of 3The controller starts redirecting to the dashboard. assertRedirect() with no path still passes. What did you fail to pin?