Commit e01b1cc2 authored by catch's avatar catch

Issue #2609680 by alexpott, jhedstrom: Add an AssertMailTrait to allow mail...

Issue #2609680 by alexpott, jhedstrom: Add an AssertMailTrait to allow mail testing in Kernel tests and fix odd stdout not found in test output
parent 79bc1638
<?php
/**
* @file
* Contains \Drupal\Core\Test\AssertMailTrait.
*/
namespace Drupal\Core\Test;
/**
* Provides methods for testing emails sent during test runs.
*/
trait AssertMailTrait {
/**
* Gets an array containing all emails sent during this test case.
*
* @param array $filter
* An array containing key/value pairs used to filter the emails that are
* returned.
*
* @return array
* An array containing email messages captured during the current test.
*/
protected function getMails(array $filter = []) {
$captured_emails = $this->container->get('state')->get('system.test_mail_collector', []);
$filtered_emails = [];
foreach ($captured_emails as $message) {
foreach ($filter as $key => $value) {
if (!isset($message[$key]) || $message[$key] != $value) {
continue 2;
}
}
$filtered_emails[] = $message;
}
return $filtered_emails;
}
/**
* Asserts that the most recently sent email message has the given value.
*
* The field in $name must have the content described in $value.
*
* @param string $name
* Name of field or message property to assert. Examples: subject, body,
* id, ...
* @param string $value
* Value of the field to assert.
* @param string $message
* (optional) A message to display with the assertion. Do not translate
* messages: use \Drupal\Component\Utility\SafeMarkup::format() to embed
* variables in the message text, not t(). If left blank, a default message
* will be displayed.
* @param string $group
* (optional) The group this message is in, which is displayed in a column
* in test output. Use 'Debug' to indicate this is debugging output. Do not
* translate this string. Defaults to 'Email'; most tests do not override
* this default.
*
* @return bool
* TRUE on pass, FALSE on fail.
*/
protected function assertMail($name, $value = '', $message = '', $group = 'Email') {
$captured_emails = $this->container->get('state')->get('system.test_mail_collector') ?: [];
$email = end($captured_emails);
return $this->assertTrue($email && isset($email[$name]) && $email[$name] == $value, $message, $group);
}
/**
* Asserts that the most recently sent email message has the string in it.
*
* @param string $field_name
* Name of field or message property to assert: subject, body, id, ...
* @param string $string
* String to search for.
* @param int $email_depth
* Number of emails to search for string, starting with most recent.
* @param string $message
* (optional) A message to display with the assertion. Do not translate
* messages: use \Drupal\Component\Utility\SafeMarkup::format() to embed
* variables in the message text, not t(). If left blank, a default message
* will be displayed.
* @param string $group
* (optional) The group this message is in, which is displayed in a column
* in test output. Use 'Debug' to indicate this is debugging output. Do not
* translate this string. Defaults to 'Other'; most tests do not override
* this default.
*
* @return bool
* TRUE on pass, FALSE on fail.
*/
protected function assertMailString($field_name, $string, $email_depth, $message = '', $group = 'Other') {
$mails = $this->getMails();
$string_found = FALSE;
// Cast MarkupInterface objects to string.
$string = (string) $string;
for ($i = count($mails) - 1; $i >= count($mails) - $email_depth && $i >= 0; $i--) {
$mail = $mails[$i];
// Normalize whitespace, as we don't know what the mail system might have
// done. Any run of whitespace becomes a single space.
$normalized_mail = preg_replace('/\s+/', ' ', $mail[$field_name]);
$normalized_string = preg_replace('/\s+/', ' ', $string);
$string_found = (FALSE !== strpos($normalized_mail, $normalized_string));
if ($string_found) {
break;
}
}
if (!$message) {
$message = format_string('Expected text found in @field of email message: "@expected".', ['@field' => $field_name, '@expected' => $string]);
}
return $this->assertTrue($string_found, $message, $group);
}
/**
* Asserts that the most recently sent email message has the pattern in it.
*
* @param string $field_name
* Name of field or message property to assert: subject, body, id, ...
* @param string $regex
* Pattern to search for.
* @param string $message
* (optional) A message to display with the assertion. Do not translate
* messages: use \Drupal\Component\Utility\SafeMarkup::format() to embed
* variables in the message text, not t(). If left blank, a default message
* will be displayed.
* @param string $group
* (optional) The group this message is in, which is displayed in a column
* in test output. Use 'Debug' to indicate this is debugging output. Do not
* translate this string. Defaults to 'Other'; most tests do not override
* this default.
*
* @return bool
* TRUE on pass, FALSE on fail.
*/
protected function assertMailPattern($field_name, $regex, $message = '', $group = 'Other') {
$mails = $this->getMails();
$mail = end($mails);
$regex_found = preg_match("/$regex/", $mail[$field_name]);
if (!$message) {
$message = format_string('Expected text found in @field of email message: "@expected".', ['@field' => $field_name, '@expected' => $regex]);
}
return $this->assertTrue($regex_found, $message, $group);
}
/**
* Outputs to verbose the most recent $count emails sent.
*
* @param int $count
* Optional number of emails to output.
*/
protected function verboseEmail($count = 1) {
$mails = $this->getMails();
for ($i = count($mails) - 1; $i >= count($mails) - $count && $i >= 0; $i--) {
$mail = $mails[$i];
$this->verbose('Email:<pre>' . print_r($mail, TRUE) . '</pre>');
}
}
}
......@@ -271,6 +271,12 @@ protected function setUp() {
// The temporary stream wrapper is able to operate both with and without
// configuration.
$this->registerStreamWrapper('temporary', 'Drupal\Core\StreamWrapper\TemporaryStream');
// Manually configure the test mail collector implementation to prevent
// tests from sending out emails and collect them in state instead.
// While this should be enforced via settings.php prior to installation,
// some tests expect to be able to test mail system implementations.
$GLOBALS['config']['system.mail']['interface']['default'] = 'test_mail_collector';
}
/**
......
......@@ -28,6 +28,7 @@
use Drupal\Core\Session\UserSession;
use Drupal\Core\Site\Settings;
use Drupal\Core\StreamWrapper\PublicStream;
use Drupal\Core\Test\AssertMailTrait;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
......@@ -47,6 +48,9 @@ abstract class WebTestBase extends TestBase {
use ContentTypeCreationTrait {
createContentType as drupalCreateContentType;
}
use AssertMailTrait {
getMails as drupalGetMails;
}
use NodeCreationTrait {
getNodeByTitle as drupalGetNodeByTitle;
createNode as drupalCreateNode;
......@@ -2518,32 +2522,6 @@ protected function assertHeader($header, $value, $message = '', $group = 'Browse
return $this->assertTrue($header_value == $value, $message ? $message : 'HTTP response header ' . $header . ' with value ' . $value . ' found, actual value: ' . $header_value, $group);
}
/**
* Gets an array containing all emails sent during this test case.
*
* @param $filter
* An array containing key/value pairs used to filter the emails that are
* returned.
*
* @return
* An array containing email messages captured during the current test.
*/
protected function drupalGetMails($filter = array()) {
$captured_emails = \Drupal::state()->get('system.test_mail_collector') ?: array();
$filtered_emails = array();
foreach ($captured_emails as $message) {
foreach ($filter as $key => $value) {
if (!isset($message[$key]) || $message[$key] != $value) {
continue 2;
}
}
$filtered_emails[] = $message;
}
return $filtered_emails;
}
/**
* Passes if the internal browser's URL matches the given path.
*
......@@ -2644,126 +2622,6 @@ protected function assertNoResponse($code, $message = '', $group = 'Browser') {
return $this->assertFalse($match, $message ? $message : SafeMarkup::format('HTTP response not expected @code, actual @curl_code', array('@code' => $code, '@curl_code' => $curl_code)), $group);
}
/**
* Asserts that the most recently sent email message has the given value.
*
* The field in $name must have the content described in $value.
*
* @param $name
* Name of field or message property to assert. Examples: subject, body,
* id, ...
* @param $value
* Value of the field to assert.
* @param $message
* (optional) A message to display with the assertion. Do not translate
* messages: use \Drupal\Component\Utility\SafeMarkup::format() to embed
* variables in the message text, not t(). If left blank, a default message
* will be displayed.
* @param $group
* (optional) The group this message is in, which is displayed in a column
* in test output. Use 'Debug' to indicate this is debugging output. Do not
* translate this string. Defaults to 'Email'; most tests do not override
* this default.
*
* @return
* TRUE on pass, FALSE on fail.
*/
protected function assertMail($name, $value = '', $message = '', $group = 'Email') {
$captured_emails = \Drupal::state()->get('system.test_mail_collector') ?: array();
$email = end($captured_emails);
return $this->assertTrue($email && isset($email[$name]) && $email[$name] == $value, $message, $group);
}
/**
* Asserts that the most recently sent email message has the string in it.
*
* @param $field_name
* Name of field or message property to assert: subject, body, id, ...
* @param $string
* String to search for.
* @param $email_depth
* Number of emails to search for string, starting with most recent.
* @param $message
* (optional) A message to display with the assertion. Do not translate
* messages: use \Drupal\Component\Utility\SafeMarkup::format() to embed
* variables in the message text, not t(). If left blank, a default message
* will be displayed.
* @param $group
* (optional) The group this message is in, which is displayed in a column
* in test output. Use 'Debug' to indicate this is debugging output. Do not
* translate this string. Defaults to 'Other'; most tests do not override
* this default.
*
* @return
* TRUE on pass, FALSE on fail.
*/
protected function assertMailString($field_name, $string, $email_depth, $message = '', $group = 'Other') {
$mails = $this->drupalGetMails();
$string_found = FALSE;
// Cast MarkupInterface objects to string.
$string = (string) $string;
for ($i = count($mails) -1; $i >= count($mails) - $email_depth && $i >= 0; $i--) {
$mail = $mails[$i];
// Normalize whitespace, as we don't know what the mail system might have
// done. Any run of whitespace becomes a single space.
$normalized_mail = preg_replace('/\s+/', ' ', $mail[$field_name]);
$normalized_string = preg_replace('/\s+/', ' ', $string);
$string_found = (FALSE !== strpos($normalized_mail, $normalized_string));
if ($string_found) {
break;
}
}
if (!$message) {
$message = format_string('Expected text found in @field of email message: "@expected".', array('@field' => $field_name, '@expected' => $string));
}
return $this->assertTrue($string_found, $message, $group);
}
/**
* Asserts that the most recently sent email message has the pattern in it.
*
* @param $field_name
* Name of field or message property to assert: subject, body, id, ...
* @param $regex
* Pattern to search for.
* @param $message
* (optional) A message to display with the assertion. Do not translate
* messages: use \Drupal\Component\Utility\SafeMarkup::format() to embed
* variables in the message text, not t(). If left blank, a default message
* will be displayed.
* @param $group
* (optional) The group this message is in, which is displayed in a column
* in test output. Use 'Debug' to indicate this is debugging output. Do not
* translate this string. Defaults to 'Other'; most tests do not override
* this default.
*
* @return
* TRUE on pass, FALSE on fail.
*/
protected function assertMailPattern($field_name, $regex, $message = '', $group = 'Other') {
$mails = $this->drupalGetMails();
$mail = end($mails);
$regex_found = preg_match("/$regex/", $mail[$field_name]);
if (!$message) {
$message = format_string('Expected text found in @field of email message: "@expected".', array('@field' => $field_name, '@expected' => $regex));
}
return $this->assertTrue($regex_found, $message, $group);
}
/**
* Outputs to verbose the most recent $count emails sent.
*
* @param $count
* Optional number of emails to output.
*/
protected function verboseEmail($count = 1) {
$mails = $this->drupalGetMails();
for ($i = count($mails) -1; $i >= count($mails) - $count && $i >= 0; $i--) {
$mail = $mails[$i];
$this->verbose('Email:<pre>' . print_r($mail, TRUE) . '</pre>');
}
}
/**
* Creates a mock request and sets it on the generator.
*
......
<?php
/**
* @file
* Contains \Drupal\KernelTests\Core\Test\AssertMailTraitTest.
*/
namespace Drupal\KernelTests\Core\Test;
use Drupal\Core\Test\AssertMailTrait;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests \Drupal\Core\Test\AssertMailTrait works.
*
* @group Test
*
* @coversDefaultClass \Drupal\Core\Test\AssertMailTrait
*/
class AssertMailTraitTest extends KernelTestBase {
use AssertMailTrait;
/**
* Tests that the maintenance theme initializes the theme and its base themes.
*/
public function testAssertMailTrait() {
/* @var \Drupal\Core\Mail\MailManagerInterface $mail_service */
$mail_service = \Drupal::service('plugin.manager.mail');
// Create an email.
$subject = $this->randomString(64);
$body = $this->randomString(128);
$message = [
'id' => 'drupal_mail_test',
'headers' => ['Content-type' => 'text/html'],
'subject' => $subject,
'to' => 'foobar@example.com',
'body' => $body,
];
// Before we send the email, \Drupal\Core\Test\AssertMailTrait::getMails()
// should return an empty array.
$captured_emails = $this->getMails();
$this->assertCount(0, $captured_emails, 'The captured emails queue is empty.');
// Send the email.
$mail_service->getInstance(['module' => 'simpletest', 'key' => 'drupal_mail_test'])->mail($message);
// Ensure that there is one email in the captured emails array.
$captured_emails = $this->getMails();
$this->assertEquals(count($captured_emails), 1, 'One email was captured.');
// Assert that the email was sent by iterating over the message properties
// and ensuring that they are captured intact.
foreach ($message as $field => $value) {
$this->assertMail($field, $value, "The email was sent and the value for property $field is intact.");
}
// Send additional emails so more than one email is captured.
for ($index = 0; $index < 5; $index++) {
$message = [
'id' => 'drupal_mail_test_' . $index,
'headers' => ['Content-type' => 'text/html'],
'subject' => $this->randomString(64),
'to' => $this->randomMachineName(32) . '@example.com',
'body' => $this->randomString(512),
];
$mail_service->getInstance(['module' => 'drupal_mail_test', 'key' => $index])->mail($message);
}
// There should now be 6 emails captured.
$captured_emails = $this->getMails();
$this->assertCount(6, $captured_emails, 'All emails were captured.');
// Test different ways of getting filtered emails via
// \Drupal\Core\Test\AssertMailTrait::getMails().
$captured_emails = $this->getMails(['id' => 'drupal_mail_test']);
$this->assertCount(1, $captured_emails, 'Only one email is returned when filtering by id.');
$captured_emails = $this->getMails(['id' => 'drupal_mail_test', 'subject' => $subject]);
$this->assertCount(1, $captured_emails, 'Only one email is returned when filtering by id and subject.');
$captured_emails = $this->getMails([
'id' => 'drupal_mail_test',
'subject' => $subject,
'from' => 'this_was_not_used@example.com',
]);
$this->assertCount(0, $captured_emails, 'No emails are returned when querying with an unused from address.');
// Send the last email again, so we can confirm that
// \Drupal\Core\Test\AssertMailTrait::getMails() filters correctly returns
// all emails with a given property/value.
$mail_service->getInstance(['module' => 'drupal_mail_test', 'key' => $index])->mail($message);
$captured_emails = $this->getMails(['id' => 'drupal_mail_test_4']);
$this->assertCount(2, $captured_emails, 'All emails with the same id are returned when filtering by id.');
}
}
......@@ -370,6 +370,12 @@ private function bootKernel() {
'class' => '\Drupal\Component\PhpStorage\FileStorage',
];
new Settings($settings);
// Manually configure the test mail collector implementation to prevent
// tests from sending out emails and collect them in state instead.
// While this should be enforced via settings.php prior to installation,
// some tests expect to be able to test mail system implementations.
$GLOBALS['config']['system.mail']['interface']['default'] = 'test_mail_collector';
}
/**
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment