The Developer Day | Staying Curious

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.

RSS Feed

5 Comments for PHPUnit email integration testing using Sendmail

Tadas | April 27, 2010 at 1:27 PM

Lots of people develop in ms windows environment + use various other MTAs on servers so this is quite limited solution. Easy and portable solution for testing this could be Python smtpd library which allows implementing minimal debugging smtp server in 10-20 lines of code.

Documentation: http://docs.python.org/library/smtpd.html

Author comment by Žilvinas Šaltys | April 27, 2010 at 1:45 PM

Thanks for your feedback Tadas. The only small problem with this this is if you have a build server which would most likely be running Linux. Which would probably would have an smtp server (probably sendmail) which is used to run other PHP scripts and possibly send legitimate emails (that’s our case). Then you get a conflict in the php.ini configuration which by default uses sendmail which you would want to change to your smtpd server. I guess it should be possible to tell sendmail to forward certain emails to the debugging smtp server.

Tadas | April 27, 2010 at 4:03 PM

Well.. Another solution: use sendmail wrapper, configure php (sendmail_path) to use it for testsuite via .htaccess (or feed custom config file if tests are executed via cli).

There are some php written scripts that behave like /usr/sbin/sendmail and output mails straight to file system or whatever you make it to ouput to.

Andrei Fedarenchyk | November 13, 2010 at 9:35 AM

Solution how to test emails within frameworks like ZF:
http://www.andfed.net/2010/11/07/zend-framework-and-phpunit-emails-testing/

Author comment by Žilvinas Šaltys | November 13, 2010 at 11:48 AM

Nice idea. We’ve replaced the test-mail sendmail solution with a different idea. When running phpunit test suite we pass a -d flag overriding the sendmail path to a php script. The php script writes to a known location on the file system. The tests then assert against that file on the filesystem.

Leave a comment!

<<

>>

Find it!

Theme Design by devolux.org