laravelmastery
SnippetsArticlesSeriesNews

Test-driven Laravel · Lesson 3 of 3

Test-driven Laravel · Lesson 3

Faking the mail

Edges and judgementTesting

The other team was refused. Sending a real email from a test is slow, and it fails when mail is not configured. Mail::fake() records the mailables instead of sending them. The test then asserts that the right one was queued, to the right address.

Video

01 Assert the mailable, not the inbox

InvoiceMail implements ShouldQueue, so the assertion is assertQueued. assertSent is for mail that goes out during the request.

PHP
it('queues the invoice email to the billing address', function () {
    Mail::fake();

    $invoice = Invoice::factory()->issued()->create();

    $this->actingAs($invoice->team->owner)
        ->post('/invoices/'.$invoice->id.'/send')
        ->assertRedirect();

    Mail::assertQueued(InvoiceMail::class, function (InvoiceMail $mail) use ($invoice) {
        return $mail->hasTo($invoice->team->billing_email);
    });
});

Fake before the request. A fake installed afterwards does not see the mail the request already handed to the real mailer.

assertQueued does not prove the template renders. Render the mailable in one test if the view is the thing you are changing. Do not render it in every test that only cares the mail was queued.

02 Render the mailable when the view is the change

One test can build the mailable and assert the HTML. It does not send, and it does not need Mail::fake(), because nothing is handed to the mailer. assertSee() checks the invoice number the template was given.

PHP
it('renders the invoice number in the mail', function () {
    $invoice = Invoice::factory()->issued()->create();

    $html = (new InvoiceMail($invoice))->render();

    expect($html)->toContain($invoice->number);
});

Keep this test next to a view change. The queued test stays the one that proves the controller asked for the mail. A failure in render() is a broken template. A failure in assertQueued() is a controller that never dispatched it.

assertNothingQueued() belongs on the test where sending would be the bug, such as a validation failure. Mail::fake() still comes first. A test that forgets the fake and expects nothing can pass because the mailer is misconfigured, which is not the same as the controller staying quiet.

Recap

You can assert that an invoice mailable was queued to the billing address without sending it.

  • assertQueued is for ShouldQueue mail. assertSent is for mail that goes during the request.
  • Fake before the request.
  • Render the mailable in the one test that is about the view.

Check yourself · 3 questions

Question 1 of 3InvoiceMail implements ShouldQueue. The test calls assertSent. What happens?

The mailable was queued, and nothing was sent. Next, assert the payment job without running it.

← PreviousAuthorisation

On this page

  • Assert the mailable, not the inbox
  • Render the mailable when the view is the change
  • 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.