17
PHPUnit email integration testing using Sendmail
5 Comments | Posted by Žilvinas Šaltys in PHP, Testing
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.
5 Comments for PHPUnit email integration testing using Sendmail

Tadas | April 27, 2010 at 1:27 PM

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/
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