TAG | mail testing integration functional phpunit
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.