MailThief by Tighten - a package for testing mail in Laravel

Matt Stauffer
3 minute read

Last week we launched a package called MailThief that's designed to make it simple to test your Laravel mail.

Laravel's current integration testing features make it easy to test that certain behaviors—visiting a route, filling out a form, etc.—will trigger certain responses. You can assert that a given event was fired, assert that the database ended up in a certain state, or even assert that a user would end up on a certain page.

But until now, you couldn't easily test that a given piece of mail was sent. MailThief makes that possible. Check out this test to see how you can easily make assertions against your application's mail.

<?php

use MailThief\Facades\MailThief;

class RegistrationTest extends TestCase
{
    public function test_new_users_are_sent_a_welcome_email()
    {
        // Block and intercept outgoing mail, important!
        MailThief::hijack();

        $this->post('register', [
            'name' => 'John Doe',
            'email' => 'john@example.com',
            'password' => 'secret',
        ]);

        // Check that an email was sent to this email address
        $this->assertTrue(
            MailThief::hasMessageFor('john@example.com')
        );

        // BCC addresses are included too
        $this->assertTrue(
            MailThief::hasMessageFor('notifications@example.com')
        );

        // Make sure the email has the correct subject
        $this->assertEquals(
            'Welcome to my app!',
            MailThief::lastMessage()->subject
        );

        // Make sure the email was sent from the correct address
        // (`from` can be a list, so we return it as a collection)
        $this->assertEquals(
            'noreply@example.com',
            MailThief::lastMessage()->from->first()
        );
    }
}

As you can see, we're expecting there to be a route defined at the URL of register that accepts POST calls. Apparently this route is expected to fire off an email welcoming a new user to the app. You could imagine a route something like this:

Route::post('register', function () {
    // ... Validation, create account, etc. ...

    Mail::send('emails.welcome', [], function ($m) {
        $email = request('email');
        $m->to($email),
        $m->subject('Welcome to my app!');
        $m->from('noreply@example.com');
        $m->bcc('notifications@example.com');
    });

    // ... Return response ...
});

I don't know about you, but this is a huge relief for me. When Adam first had this idea, I realized just how unreliable mail still feels for me. Moving mail from our local sendmail to a third-party API like Mailgun made me feel a little more hopeful about mail getting to its intended recipient, but with MailThief I now feel much more confident that my apps are actually triggering the mail I expect them to.

MailThief allows you to make assertions against the recipients, subject, content, and attachments of your mail. Check out the GitHub repo to learn more and for instructions on installing and using it.