The Developer Day | Staying Curious

TAG | mail testing integration functional phpunit

Apr/10

17

PHPUnit email integration testing using Sendmail

One of the problems when doing functional or integration testing is testing that emails are being sent out with a correct header and body. One such scenario could be a controller action which sends a password reset confirmation email and redirects to another action.

A common way to solve such a problem is to configure the local MTA to store the test emails on the file system. The following shows how this could be done using sendmail. First create a sendmail alias by editing a file located at /etc/mail/aliases and adding a line bellow other aliases:

test-mail: “| cat > /tmp/test-mail”

This tells sendmail that all incoming emails to test-mail will be written (not appended) to /tmp/test-mail. Sendmail needs to be restarted for the changes to take effect.

sudo /etc/init.d/sendmail restart

Depending on the situation it may be necessary to add the user who is going to be reading emails (for example apache) to the mail group.

sudo /usr/sbin/usermod -G mail apache

Now using PHP it should be possible to do this:

$ok = mail('test-mail', 'Hello world!', 'I am an email.');
var_dump($ok);
echo file_get_contents('/tmp/test-mail');

Further PHPUnit could be extended to add the following method to the base test case class:

public function assertEmail($attributes, $emailFilePath, 
$message = '', $delta = 0, $maxDepth = 10, 
$canonicalizeEol = FALSE, $ignoreCase = FALSE)
{
    $mailParser = new Company_Product_MailParser;
    $mailData = $mailParser->parseFile($emailFilePath);
    foreach ($attributes as $attribute => $value) {
        $constraint = new PHPUnit_Framework_Constraint_IsEqual(
            $mailData[$attribute], $delta, $maxDepth, $canonicalizeEol, $ignoreCase
        );
        $this->_test->assertThat($value, $constraint, $message);
    }
    if (is_file($emailFilePath) && is_writable($emailFilePath)) {
        unlink($emailFilePath);
    }
}

The mail parser class name explains itself:

class Company_Product_MailParser
{
    public function parseFile($mailFilePath)
    {
        $emailBody = file_get_contents($mailFilePath);
        $attributes = array(
            'to' => '',
            'from' => '',
            'date' => '',
            'subject' => '',
            'body' => ''
        );
        foreach (array_keys($attributes) as $attribute) {
            if($attribute  == 'body') {
                if (preg_match("/\n\n(.*)/", $emailBody, $matches, PREG_OFFSET_CAPTURE)) {
                    $offset = $matches[1][1];
                    $attributes[$attribute] = quoted_printable_decode(substr($emailBody, $offset));
                }
            } else {
                if (preg_match("/" . ucfirst($attribute) . ": (.*)\n/", $emailBody, $matches)) {
                    $attributes[$attribute] = $matches[1];
                }
            }
        }
        return $attributes;
    }
}

Important notice. Sendmail may not immediately send the email and it may take a few seconds for the file to appear. It may require you to add a sleep for a few seconds before the email file appears. If you find a way how it is possible to make sendmail send an email immediately please let me know.

Hide

Find it!

Theme Design by devolux.org