Mocking Swift Mailer -- 3 Steps to better code

Joe • December 21, 2016

php testing

A few days ago an issue came across my Jira queue that mentioned odd characters showing up in the subject line of our notification system. We use SwiftMailer library which is a fantastic library for sending mail.

The bug report included text similar to:

John Doe Did a thing on John's website

Something was taking our twig template that we use for our email subject and changing the encoding. This is normally done when you want to convert special characters so they're not interpreted as code blocks. The example above is showing where an apostrophe is converted to "‘", the ASCII code equivalent.

Step 1: Fix the problem.

When we create our SwiftMailer message and set the subject we change this line:

$message->setSubject($subject);

to

$message->setSubject(html_entity_decode($subject, ENT_QUOTES));

The function ‘html_entity_decode‘ is the opposite of what you've probably used ‘htmlentities‘. we're using the flag `ENT_QUOTES` so that we convert both single and double quotes that may exist in the string. The string in our case being a string of HTML rendered by Twig.

I freely admit that I tried to skip Step 2. I submitted a PR without adding a test to verify the fix. Shame on me! Luckily I have awesome coworkers who call me out.

Step 2: Write the test to verify the fix and prevent regression bugs.

The primary reason I didn't write a test for this fix was I had spent half the day spinning my wheels at our existing tests around this functionality and couldn't find a way to verify the rendered subject line after the email was sent.

Step 2.5: Get taken to school by a coworker.

When it comes to testing I've always struggled with Mocking things. I've never fully understood, it's never "clicked" for me. The previously mentioned awesome coworker Logan did a quick screen share and mocked up a test for me and we ended up doing a small refactor to inject our instance of Twig (instead of having it in a private method). Once Logan walked me through it and explained everything it all started to fall into place.

Here is our test setup() method:

public function setUp()
{
    $this->email_service = new Email;
    $this->mailer = $this->createMock(\Swift_Mailer::class);
    $this->twig_factory = $this->createMock(TwigFactory::class);

    $this->email_service->mailer = $this->mailer;
    $this->email_service->twig_factory = $this->twig_factory;
}

A neat thing Logan showed me to keep my test method cleaner was to move the assertions into their own method:

public function assertSubjectSet($subject, $data, $result)
{
    $twig = $this->createMock(\Twig_Environment::class);
    $this->twig_factory->expects($this->at(0))
        ->method('make')
        ->willReturn($twig);

    $twig->expects($this->once())->method('loadTemplate')
        ->with($subject)
        ->willReturnSelf();

    $twig->expects($this->once())
        ->method('render')
        ->with($data)
        ->willReturn($result);
}

Now for our actual test:

public function testSubjectLineMatchesExpectation()
{
    $email_data = new EmailData();
    $email_data->from_address = 'john@doe.com';
    $email_data->from_name = 'John Doe';
    $email_data->to_address = 'john@doe.com';
    $email_data->to_name = 'jane@doe.com';
    $email_data->subject = 'John Doe Did a thing on John's website';
    $email_data->body_template = 'Body of the email';
    $email_data->data = [];

    $this->assertSubjectSet($email_data->subject, $email_data->data, $email_data->subject);

    $this->mailer->expects($this->once())
        ->method('send')
        ->with($this->callback(function (\Swift_Message $actual_message) {
            $this->assertEquals('John Doe Did a thing on John\'s website', $actual_message->getSubject());

            return true;
        }));

    $this->email_service->run($email_data);
}

Step 3: Reflect and bask in the tested code glory.

Now we can properly verify that our subject lines aren't doing any bad things with encoding. No more subject lines with "' and I learned some good stuff about mocking.

Thanks for reading. Happy Coding (and Testing!).