TAG | mail testing integration functional phpunit
17
PHPUnit email integration testing using Sendmail
3 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.
