laravelmastery
SnippetsArticlesSeriesNews

Test-driven Laravel · Lesson 2 of 3

Test-driven Laravel · Lesson 2

HTTP tests for happy paths

The paths that matterTesting

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.

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.

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

PHP
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/1 is 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?

The signed-in member can follow the redirect to a saved invoice. Next, the request that should store nothing.

← PreviousActing as a user

On this page

  • Follow the redirect the controller returns
  • Follow the id the arrange already created
  • 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.