laravelmastery
SnippetsArticlesSeriesNews

Test-driven Laravel · Lesson 1 of 3

Test-driven Laravel · Lesson 1

Arrange, act, assert

The shape of a testTesting

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.

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.

PHP
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.

PHP
$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 draft status belongs only if the action promised a draft.
  • Leave created_at out 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?

The three beats are visible. Next, give each test an empty database.

← PreviousWhat a feature test is for

On this page

  • Keep the act to one call
  • Assert the thing the act was for
  • Recap
  • Check yourself

Test-driven Laravel

  • 1 Overview

The shape of a test

  • 2 What a feature test is for
  • 3 Arrange, act, assert
  • 4 Refreshing the database
  • 5 Factories that read like the domain
  • 6 Acting as a user

The paths that matter

  • 7 HTTP tests for happy paths
  • 8 Validation errors
  • 9 Authorisation

Edges and judgement

  • 10 Faking the mail
  • 11 Faking the queue
  • 12 Faking storage and HTTP
  • 13 Freezing time
  • 14 Datasets
  • 15 What not to test

Test-driven Laravel

Lessons

  1. 01Overview

The shape of a test

  1. 02What a feature test is for
  2. 03Arrange, act, assert
  3. 04Refreshing the database
  4. 05Factories that read like the domain
  5. 06Acting as a user

The paths that matter

  1. 07HTTP tests for happy paths
  2. 08Validation errors
  3. 09Authorisation

Edges and judgement

  1. 10Faking the mail
  2. 11Faking the queue
  3. 12Faking storage and HTTP
  4. 13Freezing time
  5. 14Datasets
  6. 15What not to test

Get the Friday email

One snippet and the week’s Laravel news, in your inbox every Friday morning. Unsubscribe whenever you like.

laravelmastery

Practical Laravel learning for working developers, made in the UK.

Learn

  • Snippets
  • Articles
  • Series

Stay current

  • News
  • Friday email
  • RSS feed

Company

  • About
  • Privacy
  • Terms
© 2026 Laravel Mastery. Independent and not affiliated with Laravel Holdings Inc.Designed and built by Half Shell Studios

We remember the videos you watch, where you left off, and which series lessons you mark as read, against this connection’s address. Nothing is stored until you agree.