Skip to content

Improve Mailable assertion error messages with expected vs actual values #56221

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 49 additions & 13 deletions src/Illuminate/Mail/Mailable.php
Original file line number Diff line number Diff line change
Expand Up @@ -1219,11 +1219,12 @@ public function assertFrom($address, $name = null)
{
$this->renderForAssertions();

$recipient = $this->formatAssertionRecipient($address, $name);
$expected = $this->formatAssertionRecipient($address, $name);
$actual = $this->formatActualRecipients($this->from);

PHPUnit::assertTrue(
$this->hasFrom($address, $name),
"Email was not from expected address [{$recipient}]."
"Email was not from expected address.\nExpected: [{$expected}]\nActual: [{$actual}]"
);

return $this;
Expand All @@ -1240,11 +1241,12 @@ public function assertTo($address, $name = null)
{
$this->renderForAssertions();

$recipient = $this->formatAssertionRecipient($address, $name);
$expected = $this->formatAssertionRecipient($address, $name);
$actual = $this->formatActualRecipients($this->to);

PHPUnit::assertTrue(
$this->hasTo($address, $name),
"Did not see expected recipient [{$recipient}] in email 'to' recipients."
"Did not see expected recipient in email 'to' recipients.\nExpected: [{$expected}]\nActual: [{$actual}]"
);

return $this;
Expand Down Expand Up @@ -1273,11 +1275,12 @@ public function assertHasCc($address, $name = null)
{
$this->renderForAssertions();

$recipient = $this->formatAssertionRecipient($address, $name);
$expected = $this->formatAssertionRecipient($address, $name);
$actual = $this->formatActualRecipients($this->cc);

PHPUnit::assertTrue(
$this->hasCc($address, $name),
"Did not see expected recipient [{$recipient}] in email 'cc' recipients."
"Did not see expected recipient in email 'cc' recipients.\nExpected: [{$expected}]\nActual: [{$actual}]"
);

return $this;
Expand All @@ -1294,11 +1297,12 @@ public function assertHasBcc($address, $name = null)
{
$this->renderForAssertions();

$recipient = $this->formatAssertionRecipient($address, $name);
$expected = $this->formatAssertionRecipient($address, $name);
$actual = $this->formatActualRecipients($this->bcc);

PHPUnit::assertTrue(
$this->hasBcc($address, $name),
"Did not see expected recipient [{$recipient}] in email 'bcc' recipients."
"Did not see expected recipient in email 'bcc' recipients.\nExpected: [{$expected}]\nActual: [{$actual}]"
);

return $this;
Expand All @@ -1315,11 +1319,12 @@ public function assertHasReplyTo($address, $name = null)
{
$this->renderForAssertions();

$replyTo = $this->formatAssertionRecipient($address, $name);
$expected = $this->formatAssertionRecipient($address, $name);
$actual = $this->formatActualRecipients($this->replyTo);

PHPUnit::assertTrue(
$this->hasReplyTo($address, $name),
"Did not see expected address [{$replyTo}] as email 'reply to' recipient."
"Did not see expected address as email 'reply to' recipient.\nExpected: [{$expected}]\nActual: [{$actual}]"
);

return $this;
Expand All @@ -1345,6 +1350,28 @@ private function formatAssertionRecipient($address, $name = null)
return $address;
}

/**
* Format actual recipients for display in assertion messages.
*
* @param array $recipients
* @return string
*/
private function formatActualRecipients($recipients)
{
if (empty($recipients)) {
return 'none';
}

return (new Collection($recipients))->map(function ($recipient) {
$formatted = $recipient['address'];
if (! empty($recipient['name'])) {
$formatted .= ' ('.$recipient['name'].')';
}

return $formatted;
})->implode(', ');
}

/**
* Assert that the mailable has the given subject.
*
Expand All @@ -1355,9 +1382,11 @@ public function assertHasSubject($subject)
{
$this->renderForAssertions();

$actualSubject = $this->subject ?: (method_exists($this, 'envelope') ? $this->envelope()->subject : null) ?: Str::title(Str::snake(class_basename($this), ' '));

PHPUnit::assertTrue(
$this->hasSubject($subject),
"Did not see expected text [{$subject}] in email subject."
"Email subject does not match expected value.\nExpected: [{$subject}]\nActual: [{$actualSubject}]"
);

return $this;
Expand Down Expand Up @@ -1570,9 +1599,12 @@ public function assertHasTag($tag)
{
$this->renderForAssertions();

$actualTags = method_exists($this, 'envelope') ? array_merge($this->tags, $this->envelope()->tags) : $this->tags;
$actualTagsString = empty($actualTags) ? 'none' : implode(', ', $actualTags);

PHPUnit::assertTrue(
$this->hasTag($tag),
"Did not see expected tag [{$tag}] in email tags."
"Did not see expected tag in email tags.\nExpected: [{$tag}]\nActual: [{$actualTagsString}]"
);

return $this;
Expand All @@ -1589,9 +1621,13 @@ public function assertHasMetadata($key, $value)
{
$this->renderForAssertions();

$actualMetadata = method_exists($this, 'envelope') ? array_merge($this->metadata, $this->envelope()->metadata) : $this->metadata;
$actualValue = $actualMetadata[$key] ?? null;
$actualString = $actualValue !== null ? "[{$key}] => [{$actualValue}]" : "key [{$key}] not found";

PHPUnit::assertTrue(
$this->hasMetadata($key, $value),
"Did not see expected key [{$key}] and value [{$value}] in email metadata."
"Email metadata does not match expected value.\nExpected: [{$key}] => [{$value}]\nActual: {$actualString}"
);

return $this;
Expand Down
26 changes: 13 additions & 13 deletions tests/Mail/MailMailableTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public function testMailableSetsRecipientsCorrectly()
$mailable->assertHasTo('[email protected]', 'Taylor Otwell');
$this->fail();
} catch (AssertionFailedError $e) {
$this->assertSame("Did not see expected recipient [[email protected] (Taylor Otwell)] in email 'to' recipients.\nFailed asserting that false is true.", $e->getMessage());
$this->assertSame("Did not see expected recipient in email 'to' recipients.\nExpected: [[email protected] (Taylor Otwell)]\nActual: [[email protected]]\nFailed asserting that false is true.", $e->getMessage());
}

$mailable = new WelcomeMailableStub;
Expand Down Expand Up @@ -101,7 +101,7 @@ public function testMailableSetsRecipientsCorrectly()
if (! is_string($address)) {
$address = json_encode($address);
}
$this->assertSame("Did not see expected recipient [{$address}] in email 'to' recipients.\nFailed asserting that false is true.", $e->getMessage());
$this->assertSame("Did not see expected recipient in email 'to' recipients.\nExpected: [{$address}]\nActual: [none]\nFailed asserting that false is true.", $e->getMessage());
}
}
}
Expand Down Expand Up @@ -134,7 +134,7 @@ public function testMailableSetsCcRecipientsCorrectly()
$mailable->assertHasCc('[email protected]', 'Taylor Otwell');
$this->fail();
} catch (AssertionFailedError $e) {
$this->assertSame("Did not see expected recipient [[email protected] (Taylor Otwell)] in email 'cc' recipients.\nFailed asserting that false is true.", $e->getMessage());
$this->assertSame("Did not see expected recipient in email 'cc' recipients.\nExpected: [[email protected] (Taylor Otwell)]\nActual: [[email protected]]\nFailed asserting that false is true.", $e->getMessage());
}

$mailable = new WelcomeMailableStub;
Expand Down Expand Up @@ -192,7 +192,7 @@ public function testMailableSetsCcRecipientsCorrectly()
if (! is_string($address)) {
$address = json_encode($address);
}
$this->assertSame("Did not see expected recipient [{$address}] in email 'cc' recipients.\nFailed asserting that false is true.", $e->getMessage());
$this->assertSame("Did not see expected recipient in email 'cc' recipients.\nExpected: [{$address}]\nActual: [none]\nFailed asserting that false is true.", $e->getMessage());
}
}
}
Expand Down Expand Up @@ -225,7 +225,7 @@ public function testMailableSetsBccRecipientsCorrectly()
$mailable->assertHasBcc('[email protected]', 'Taylor Otwell');
$this->fail();
} catch (AssertionFailedError $e) {
$this->assertSame("Did not see expected recipient [[email protected] (Taylor Otwell)] in email 'bcc' recipients.\nFailed asserting that false is true.", $e->getMessage());
$this->assertSame("Did not see expected recipient in email 'bcc' recipients.\nExpected: [[email protected] (Taylor Otwell)]\nActual: [[email protected]]\nFailed asserting that false is true.", $e->getMessage());
}

$mailable = new WelcomeMailableStub;
Expand Down Expand Up @@ -283,7 +283,7 @@ public function testMailableSetsBccRecipientsCorrectly()
if (! is_string($address)) {
$address = json_encode($address);
}
$this->assertSame("Did not see expected recipient [{$address}] in email 'bcc' recipients.\nFailed asserting that false is true.", $e->getMessage());
$this->assertSame("Did not see expected recipient in email 'bcc' recipients.\nExpected: [{$address}]\nActual: [none]\nFailed asserting that false is true.", $e->getMessage());
}
}
}
Expand Down Expand Up @@ -316,7 +316,7 @@ public function testMailableSetsReplyToCorrectly()
$mailable->assertHasReplyTo('[email protected]', 'Taylor Otwell');
$this->fail();
} catch (AssertionFailedError $e) {
$this->assertSame("Did not see expected address [[email protected] (Taylor Otwell)] as email 'reply to' recipient.\nFailed asserting that false is true.", $e->getMessage());
$this->assertSame("Did not see expected address as email 'reply to' recipient.\nExpected: [[email protected] (Taylor Otwell)]\nActual: [[email protected]]\nFailed asserting that false is true.", $e->getMessage());
}

$mailable = new WelcomeMailableStub;
Expand Down Expand Up @@ -363,7 +363,7 @@ public function testMailableSetsReplyToCorrectly()
if (! is_string($address)) {
$address = json_encode($address);
}
$this->assertSame("Did not see expected address [{$address}] as email 'reply to' recipient.\nFailed asserting that false is true.", $e->getMessage());
$this->assertSame("Did not see expected address as email 'reply to' recipient.\nExpected: [{$address}]\nActual: [none]\nFailed asserting that false is true.", $e->getMessage());
}
}
}
Expand Down Expand Up @@ -396,7 +396,7 @@ public function testMailableSetsFromCorrectly()
$mailable->assertFrom('[email protected]', 'Taylor Otwell');
$this->fail();
} catch (AssertionFailedError $e) {
$this->assertSame("Email was not from expected address [[email protected] (Taylor Otwell)].\nFailed asserting that false is true.", $e->getMessage());
$this->assertSame("Email was not from expected address.\nExpected: [[email protected] (Taylor Otwell)]\nActual: [[email protected]]\nFailed asserting that false is true.", $e->getMessage());
}

$mailable = new WelcomeMailableStub;
Expand Down Expand Up @@ -443,7 +443,7 @@ public function testMailableSetsFromCorrectly()
if (! is_string($address)) {
$address = json_encode($address);
}
$this->assertSame("Email was not from expected address [{$address}].\nFailed asserting that false is true.", $e->getMessage());
$this->assertSame("Email was not from expected address.\nExpected: [{$address}]\nActual: [none]\nFailed asserting that false is true.", $e->getMessage());
}
}
}
Expand Down Expand Up @@ -629,7 +629,7 @@ public function testMailableMetadataGetsSent()
$mailable->assertHasMetadata('test', 'test');
$this->fail();
} catch (AssertionFailedError $e) {
$this->assertSame("Did not see expected key [test] and value [test] in email metadata.\nFailed asserting that false is true.", $e->getMessage());
$this->assertSame("Email metadata does not match expected value.\nExpected: [test] => [test]\nActual: key [test] not found\nFailed asserting that false is true.", $e->getMessage());
}
}

Expand Down Expand Up @@ -664,7 +664,7 @@ public function testMailableTagGetsSent()
$mailable->assertHasTag('bar');
$this->fail();
} catch (AssertionFailedError $e) {
$this->assertSame("Did not see expected tag [bar] in email tags.\nFailed asserting that false is true.", $e->getMessage());
$this->assertSame("Did not see expected tag in email tags.\nExpected: [bar]\nActual: [test, foo]\nFailed asserting that false is true.", $e->getMessage());
}
}

Expand Down Expand Up @@ -1088,7 +1088,7 @@ public function build()
$mailable->assertHasSubject('Foo Subject');
$this->fail();
} catch (AssertionFailedError $e) {
$this->assertSame("Did not see expected text [Foo Subject] in email subject.\nFailed asserting that false is true.", $e->getMessage());
$this->assertStringContainsString("Email subject does not match expected value.\nExpected: [Foo Subject]\nActual:", $e->getMessage());
}

$mailable = new class() extends Mailable
Expand Down