zenstruck/mailer-test
Opinionated helpers to test emails sent with symfony/mailer. Use InteractsWithMailer in KernelTestCase/WebTestCase for fluent assertions like assertNoEmailSent, assertSentEmailCount, and inspect messages with TestEmail (subject, recipients, body, attachments).
Installation:
composer require --dev zenstruck/mailer-test
Enable the bundle in your tests/config/packages/test/_mailer.yaml:
imports:
- { resource: "@ZenstruckMailerTestBundle/Resources/config/test.yaml" }
First Test Case:
Extend KernelTestCase or WebTestCase and use the InteractsWithMailer trait:
use Zenstruck\Mailer\Test\InteractsWithMailer;
class EmailTest extends KernelTestCase
{
use InteractsWithMailer;
public function test_basic_email_sent()
{
Mailer::to('user@example.com')->send(new YourEmail());
$this->mailer()->assertEmailSentTo('user@example.com');
}
}
public function test_email_content()
{
Mailer::to('user@example.com')->send(new YourEmail());
$this->mailer()->assertEmailSentTo('user@example.com', function(TestEmail $email) {
$email
->assertSubject('Welcome!')
->assertTextContains('Thank you for registering')
->assertHasFile('welcome.pdf');
});
}
Trigger Email Sending:
$this->client->request('POST', '/register', [
'email' => 'user@example.com'
]);
Assert Email Sent:
$this->mailer()->assertEmailSentTo('user@example.com');
Deep Assertions:
$this->mailer()->assertEmailSentTo('user@example.com', function(TestEmail $email) {
$email
->assertSubjectContains('Registration')
->assertFrom('no-reply@example.com')
->assertHasTag('welcome');
});
// Filter by subject
$sentEmails = $this->mailer()->sentEmails()
->whereSubjectContains('Password')
->assertCount(1);
// Filter by recipient
$sentEmails = $this->mailer()->sentEmails()
->whereTo('admin@example.com')
->each(function(TestEmail $email) {
$email->assertSubject('Admin Notification');
});
// Using MailerComponent
$browser->withProfiling()
->visit('/forgot-password')
->use(function(MailerComponent $mailer) {
$mailer->assertEmailSentTo('user@example.com');
});
class AppTestEmail extends TestEmail
{
public function assertHasCustomHeader(string $header, string $value): self
{
return $this->assertHeader($header, $value);
}
}
Kernel Reset: Emails persist between kernel reboots. Always reset after tests:
$this->mailer()->reset();
Profiler Dependency: Browser-based assertions require the profiler. Enable it explicitly:
$browser->withProfiling();
Type Safety:
Custom TestEmail classes must be explicitly type-hinted in callbacks:
// Wrong (uses default TestEmail)
$this->mailer()->assertEmailSentTo('user@example.com', function($email) { ... });
// Correct
$this->mailer()->assertEmailSentTo('user@example.com', function(AppTestEmail $email) { ... });
$this->mailer()->sentEmails()->dump();
$email->getHeaders()->get('X-Custom-Header');
TestEmail to add domain-specific checks (e.g., assertHasPostmarkTag).reset() in a base test case for shared test suites.MailerExtension to embed assertions directly into custom browsers.sentEmails()->all() in large test suites. Pre-filter with where() instead.How can I help you explore Laravel packages today?