laravelmastery
SnippetsArticlesSeriesNews

Test-driven Laravel · Lesson 2 of 3

Test-driven Laravel · Lesson 2

Authorisation

The paths that matterTesting

Validation named the fields and stored nothing. A person on another team must not open this invoice. The test signs them in, requests the invoice, and expects 403. A second test signs in someone on the owning team and expects 200. Those are two behaviours.

Video

01 Assert the status the policy produces

Build the invoice for one team. Act as a user whose current team is a different one.

PHP
it('hides an invoice from another team', function () {
    $invoice = Invoice::factory()->create();
    $other = User::factory()->create();

    $this->actingAs($other)
        ->get('/invoices/'.$invoice->id)
        ->assertForbidden();
});

assertForbidden() is the 403 from the policy. assertNotFound() is a different choice: it hides the fact that the invoice exists. Pick the one the route actually returns, and assert that.

The policies article is the decision between a policy and a gate, with the team check written out.

Policies vs gates: choosing the right authorisation tool.

Do not only call $user->can('view', $invoice) and stop. That misses a route that forgot to authorise. The HTTP test is the one that would catch it.

02 The owning team is allowed in

The forbidden test is only half the policy. A member of the invoice’s team should get the page. If both tests return 403, the policy is denying everyone. If both return 200, the team check is missing.

PHP
it('shows an invoice to its own team', function () {
    $invoice = Invoice::factory()->create();

    $this->actingAs($invoice->team->owner)
        ->get('/invoices/'.$invoice->id)
        ->assertOk();
});

Use the owner the factory already attached. Creating a second user and setting current_team_id by hand repeats the arrange the factory was for. assertOk() is enough here. The forbidden test already proved the other team cannot see the same URL.

Update and delete are separate abilities, so they are separate tests. A policy that allows view and denies update will not be caught by a GET. Post to the update route as the other team and assertForbidden() again. The owning team’s update test asserts the redirect and the changed column. One status code cannot stand in for both.

Recap

You can prove another team is forbidden and the owning team is allowed.

  • assertForbidden() is the policy's 403. assertNotFound() is a different product choice.
  • A test that only calls can() misses a route that forgot to authorise.
  • If both tests are 403, the policy denies everyone. If both are 200, the team check is missing.

Check yourself · 3 questions

Question 1 of 3Only the other-team test exists, and it gets 403. The owner also gets 403. What do you not know yet?

Another team gets 403, and the owner gets through. Next, assert the email without sending it.

← PreviousValidation errors

On this page

  • Assert the status the policy produces
  • The owning team is allowed in
  • 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.