The feature test checks one outcome through HTTP. Every test in this series has the same three beats. Arrange the world, do the one thing, then assert what changed. The blank lines make the beats visible, which is the point of the structure.
Test-driven Laravel · Lesson 1
Arrange, act, assert
Video
01 Keep the act to one call
The arrange is a user and, when you need one, an invoice. The act is a single HTTP call. The assert is the status and the row you expect to find.
it('stores the invoice number', function () {
$user = User::factory()->create();
$this->actingAs($user)
->post('/invoices', [
'number' => 'INV-1042',
'issued_on' => '2026-10-22',
]);
$this->assertDatabaseHas('invoices', [
'number' => 'INV-1042',
'team_id' => $user->current_team_id,
]);
});If the arrange is longer than the act and the assert together, the test is setting up something it does not care about. Move that setup into a factory state, in Factories that read like the domain, rather than into more lines in the test.
02 Assert the thing the act was for
The post above is about the number and the team. A second assertion that the status is draft belongs in this test only when the action promised a draft. Adding it because the column exists makes the test fail for a later change that this test was not written to protect.
$this->assertDatabaseHas('invoices', [
'number' => 'INV-1042',
'status' => 'draft',
'team_id' => $user->current_team_id,
]);Leave created_at out. The clock moves, and this test did not freeze it. Leave the lines relationship out. The post did not send any lines. An assert that reaches for them is arrange you forgot to write, or an outcome the act never produced. Blank lines between the three beats are enough structure. Comments that say arrange, act and assert are not.
Recap
You can keep a test to three beats: the world, one request, the row you meant to change.
- The act is a single HTTP call.
- Assert the columns the act was for. A
draftstatus belongs only if the action promised a draft. - Leave
created_atout unless you froze the clock.
Check yourself · 3 questions
Question 1 of 3The arrange builds ten related models the assertion never reads. What should move?