Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Testing Emails

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $10.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

When we go into lock down, we need to send an email. Before we write the code to do that, let's add an assertion for it.

Asserting an Email is Sent

How? Symfony has our back: it gives us a few methods related to emails, like $this->assertEmailCount(). We can assert a lot of things about emails, but for simplicity's sake, we'll stick to this simple count.

... lines 1 - 12
class LockDownHelperTest extends KernelTestCase
... lines 15 - 33
public function testDinoEscapedPersistsLockDown()
... lines 36 - 39
... lines 42 - 46

Run the test:

symfony php vendor/bin/phpunit tests/Integration/Service/LockDownHelperTest.php

Epic fail, because... we don't even have mailer installed yet. Let's do that! Run:

composer require symfony/mailer

If it asks about Docker configuration, that's up to you, but I'm going to say Yes permanently. We'll talk about what that did in a minute, but it's not super important.

Similar to a database, we need to configure our Mailer connection parameters. That's done in .env via MAILER_DSN. Uncomment this. The null transport is a great default. It means that emails won't actually be sent in the dev or test environments. And then you can override on your production environment to set it to something real.

35 lines .env
... lines 1 - 32
... lines 34 - 35

If you do want to change this to something else in the dev environment, I would probably add this null transport to .env.test... because it's really nice to avoid sending any emails from our tests.

Alright, roll the testing dice again:

symfony php vendor/bin/phpunit tests/Integration/Service/LockDownHelperTest.php

Better! It fails because we haven't sent any emails. Let's do that!

Sending the Email

Over in LockDownHelper, autowire one more service: private MailerInterface $mailer. Then, down here, since this isn't a Mailer tutorial, call a new sendEmailAlert() method... and I'll paste that in. Hover over the Email class and hit "alt" + "enter" to add the Symfony\Component\Mime\Email use statement.

... lines 1 - 8
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Email;
... line 11
class LockDownHelper
public function __construct(
... lines 15 - 17
private MailerInterface $mailer
... lines 22 - 35
public function dinoEscaped(): void
... lines 38 - 42
private function sendEmailAlert(): void
$email = (new Email())
->subject('PARK LOCKDOWN')

All set! Hustle back to the command-line:

symfony php vendor/bin/phpunit tests/Integration/Service/LockDownHelperTest.php

Got it! The test passes!

Seeing Emails via MailCatcher

By the way, this isn't related to testing, but one cool things about using the Docker integration is, when we installed Mailer, it added this mailcatcher service.

... lines 1 - 2
... lines 4 - 10
image: schickling/mailcatcher
ports: ["1025", "1080"]
... lines 14 - 15


docker compose down


docker compose up -d

to start the new service. Then run the test again. It still passes. However, because the mailcatcher service is running and we executed our tests through the Symfony binary, it overrode the MAILER_DSN environment variable and pointed it at MailCatcher. What... is MailCatcher?

To find out, run:

symfony open:local:webmail

Sweet! MailCatcher is a fake email service with a little web GUI to see the emails your app has sent. If we sent an email via our real app, that would show up here.

Watch. Run:

symfony console app:lockdown:start

Lockdown! And when you check MailCatcher... ha! We have two messages! Pretty cool!

Using zenstruck/mailer-test

Anyway, before we stop talking about emails, I want to show you one more tool. And it's another library from Zenstruck. Run:

composer require zenstruck/mailer-test --dev

Symfony has built-in tools for testing emails, and they work great. This mailer-test library gives us even more tools, and it's simple to use!

Add another trait to our test - use InteractsWithMailer - and then, down here, instead of assertEmailCount, we can say $this->mailer()->... and then, woh, we have a ton of different asserts at our disposal. Say ->assertSentEmailCount(1), and below that, assertEmailSentTo() with staff@dinotopia.com and Subject line PARK LOCKDOWN. Whoops! Let me fix my typo. You can see that this is the expectedTo and then this is a callable where we could assert more things or just pass the expected subject.

... lines 1 - 11
use Zenstruck\Mailer\Test\InteractsWithMailer;
... line 13
class LockDownHelperTest extends KernelTestCase
... line 16
use InteractsWithMailer;
... lines 18 - 35
public function testDinoEscapedPersistsLockDown()
... lines 38 - 41
$this->mailer()->assertEmailSentTo('staff@dinotopia.com', 'PARK LOCKDOWN');
... lines 45 - 49

This is pretty simple, but it's one of the many things we can do with this library. Check out the docs to learn about everything.

Run the test again:

symfony php vendor/bin/phpunit tests/Integration/Service/LockDownHelperTest.php

All good! Next up: let's talk about testing messenger.

Leave a comment!

Login or Register to join the conversation
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial uses PHPUnit 9 but works just fine for PHPUnit 10.

What PHP libraries does this tutorial use?

// composer.json
    "require": {
        "php": ">=8.1.0",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "doctrine/doctrine-bundle": "^2.8", // 2.10.2
        "doctrine/doctrine-migrations-bundle": "^3.2", // 3.2.4
        "doctrine/orm": "^2.14", // 2.16.2
        "symfony/asset": "6.3.*", // v6.3.0
        "symfony/console": "6.3.*", // v6.3.4
        "symfony/dotenv": "6.3.*", // v6.3.0
        "symfony/flex": "^2", // v2.3.3
        "symfony/framework-bundle": "6.3.*", // v6.3.5
        "symfony/http-client": "6.3.*", // v6.3.5
        "symfony/mailer": "6.3.*", // v6.3.5
        "symfony/messenger": "6.3.*", // v6.3.5
        "symfony/monolog-bundle": "^3.0", // v3.8.0
        "symfony/runtime": "6.3.*", // v6.3.2
        "symfony/security-csrf": "6.3.*", // v6.3.2
        "symfony/twig-bundle": "6.3.*", // v6.3.0
        "symfony/yaml": "6.3.*" // v6.3.3
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.4", // 3.4.4
        "phpunit/phpunit": "^9.5", // 9.6.13
        "symfony/browser-kit": "6.3.*", // v6.3.2
        "symfony/css-selector": "6.3.*", // v6.3.2
        "symfony/debug-bundle": "6.3.*", // v6.3.2
        "symfony/maker-bundle": "^1.48", // v1.51.1
        "symfony/phpunit-bridge": "^6.2", // v6.3.2
        "symfony/stopwatch": "6.3.*", // v6.3.0
        "symfony/web-profiler-bundle": "6.3.*", // v6.3.2
        "zenstruck/foundry": "^1.35", // v1.35.0
        "zenstruck/mailer-test": "^1.3", // v1.3.0
        "zenstruck/messenger-test": "^1.7" // v1.7.3