Testing emails in Laravel

Software testing is a key aspect of writing an high-quality code, and helps ensure that any future changes don’t unexpectedly break anything.  Automation increases the repeatability of this task and is easily applied to Laravel.

Many modern web apps use email to send welcome information or notifications to users based upon certain events.  Even with a well setup test environment, you will want to stop possible hundreds or thousands of test emails being sent.  And there is an easy solution.

Mocking

A Mock Object is designed to simulate the behaviour of the real object but with out the actual functionality. It enables you to place it in many states to test behaviour in cases which you hope would never occur in production and are difficult to replicate in a test environment.

Laravel documentation often refers to Mocking as Faking. They are not technically the same thing, but that is an argument where the two sides are never going to agree. For this purpose, however, the two names are analogous.

Scenario

Lets say you are using Laravel’s user model with Email Verification. This was introduced in 5.7 to enable the protection of routes against users who may have registered but have not got a valid email address. i.e. Spam.

When a user registers, they should automatically be sent an email via the listener (I shall cover over-riding this listener in a separate post). You could just setup mailtrap.io or similar and see the results for yourself, but a phpunit test is more repeatable.

Faking Laravel Mail

As with much of Laravel, faking mail in a test case is simplicity itself.

use Illuminate\Auth\Notifications\VerifyEmail;
use Illuminate\Support\Facades\Mail;

public function testNewUserEmail()
{
   // Setup the mock object
   Mail::fake();

   // Call your business logic or factory
   $user = factory(User::class)->create();

   // Test that the email has been sent
   Mail::assertSent(VerifyEmail::class);
}

In the above example, the assertion is that a mailable of the VerifyEmail class was sent. If you use queues instead of direct sending, then you would replace Mail::assertSent() with Mail::assertQueued().

There are also some inverse assertions – Mail::assertNotSent() and Mail::assertNotQueued().