Image by Tumisu — https://pixabay.com/en/email-marketing-online-marketing-2362038/

Laravel Mail Assertions

Matt Kingshott
Feb 12, 2018 · 4 min read

Today, we’re releasing a new open-source package that aims to make it a lot simpler to test for emails being sent by your application.

You can find it here: https://github.com/bmelon/laravel-mail-assertions
Read on to discover how much easier it can make email testing.

PROMO: Check out our startup https://ceed.io — A template-driven script / code generator you can use to create common files such as forms, repositories, factories, validations etc. Execute the scripts in the browser, cURL or your IDE.


Laravel is a fantastic framework that makes all areas of web development effortlessly easy compared to many other setups. However, there was always one thing that, in my opinion, felt laborious, and that was the process of testing the emails your application is sending.

While a huge amount of testing functionality is directly available through the comprehensive assertions provided by the Laravel TestCase, email was a completely different story:

  1. First, you’d have to import the Mail facade.
  2. Then, import any mailable classes you want to test.
  3. Call the fake() method on the Mail facade in each test.
  4. Call the assertSent() method on the Mail facade, reference the mailable class, pass any parameters through a closure.
  5. And then finally, execute the assertions themselves.

And, it doesn’t stop there, you’d have to do the same all over again if you wanted to test for an email that exists as a notification.

It’s also worth noting that the assertion library is not complete, there are some things you can’t test for (the body of the email, for example). Chaining is also a problem to some extent, though less so than the missing methods.


While it’s true that with some complex tests, you may need deep access to the mailable, a lot of the time, we just need to test that an email was sent, and that it had the right subject, body and to fields set.

For these scenarios, it would be much more preferable to be able to execute assertions directly on the test class instance and be done with it. That’s where this package comes in. So, let’s take a look at it…


As you would expect, you can pull the package in with composer:

composer require bmelon/laravel-mail-assertions

Next, add the trait to the TestCase.php file in your tests directory:

namespace Tests;

use BlueMelon\MailAssertions\Assertions as MailAssertions;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;

abstract class TestCase extends BaseTestCase
{
use CreatesApplication, MailAssertions;
}

Lastly, if you haven’t already done so, you should configure your phpunit.xml file to use the log mail driver:

<php>
<env name="MAIL_DRIVER" value="log"/>
</php>

Other drivers may work, but are not supported and haven’t been tested.


As you would expect, you can call the included assertions directly:

$this -> assertEmailSent();

Since all of the assertions return the class instance, you can also chain multiple assertions together to create fluent code:

$this -> assertEmailSent()
-> assertEmailFrom("john@example.com");

You can view the full list of available assertions on Github, but sufficed to say, all the usual suspects are present (from, to, cc, bcc, replyTo, subject, body, count, sent or not sent).


Since it is possible for a test to result in multiple emails being sent, the package uses the concept of a stack (array) to store all of the outgoing emails.

Sometimes, it may be necessary to perform one or more assertions, then empty the stack, before proceeding to execute additional code, which will result in new emails being sent / needing to be tested.

For these scenarios, you can call the flushEmail() method.


Sometimes, it may be necessary to perform one or more assertions on a particular email in the stack. You can do so by calling the getEmailByIndex() method and supplying it as the second parameter to any assertion method:

$this -> assertEmailFrom(
"john@example.com",
$this -> getEmailByIndex(3)
);

If you do not supply a second parameter, the assertions will instead operate on the most recent email in the stack.


If you’re still not convinced / are unsure about whether to use this approach, here are a few things you might want to consider:

  1. Your tests are much easier to read.
  2. Your tests require substantially less code.
  3. You can chain assertions to create more fluent code.
  4. Your tests become less brittle as they don’t depend upon namespaces and classes. You can move everything around in your application and not have to modify the tests (assuming moving / renaming is all that took place).

That’s pretty much everything you need to know. Head over to Github to view the more detailed readme / get started.

We hope you find the package useful… if you do, you can always say thanks by buying us a coffee. It would be very much appreciated. Thanks!

Follow us on Twitter.

Matt Kingshott

Written by

Senior developer at @alphametric_co. Generally working with PHP / Laravel / Vue. Open source fanatic.