Skip to content

Commit 250f628

Browse files
krricoTavoNiievez
andauthored
Support RawMessage instead of Email for Mail/Mime assertions (#232)
* adjusted type hints of Mailer and Mime assertions traits to support RawMessage and Message instead of Email only * Enhance mailer assertions to support Message objects --------- Co-authored-by: Rico Kritz <> Co-authored-by: Aaron Gustavo Nieves <ganieves@outlook.com>
1 parent bcb9191 commit 250f628

8 files changed

Lines changed: 178 additions & 64 deletions

File tree

src/Codeception/Module/Symfony/MailerAssertionsTrait.php

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
use Symfony\Component\Mailer\Event\MessageEvents;
1111
use Symfony\Component\Mailer\EventListener\MessageLoggerListener;
1212
use Symfony\Component\Mailer\Test\Constraint as MailerConstraint;
13-
use Symfony\Component\Mime\Email;
13+
use Symfony\Component\Mime\RawMessage;
1414

1515
use function array_key_last;
1616

@@ -88,24 +88,21 @@ public function dontSeeEmailIsSent(): void
8888
}
8989

9090
/**
91-
* Returns the last sent email.
91+
* Returns the last sent message or `null` if no message was sent.
92+
* The return type is `RawMessage`, which covers both `Email` and `Message` objects.
9293
* The function is based on `\Symfony\Component\Mailer\EventListener\MessageLoggerListener`, which means:
9394
* If your app performs an HTTP redirect after sending the email, you need to suppress it using [stopFollowingRedirects()](#stopFollowingRedirects) first.
9495
* See also: [grabSentEmails()](https://codeception.com/docs/modules/Symfony#grabSentEmails)
9596
*
9697
* ```php
9798
* <?php
9899
* $email = $I->grabLastSentEmail();
99-
* $address = $email->getTo()[0];
100-
* $I->assertSame('john_doe@example.com', $address->getAddress());
100+
* $I->assertEmailHasHeader('To', $email);
101101
* ```
102102
*/
103-
public function grabLastSentEmail(): ?Email
103+
public function grabLastSentEmail(): ?RawMessage
104104
{
105-
/** @var Email[] $emails */
106-
$emails = $this->getMessageMailerEvents()->getMessages();
107-
108-
return $emails ? $emails[array_key_last($emails)] : null;
105+
return $this->grabLastSentRawMessage();
109106
}
110107

111108
/**
@@ -119,7 +116,7 @@ public function grabLastSentEmail(): ?Email
119116
* $emails = $I->grabSentEmails();
120117
* ```
121118
*
122-
* @return \Symfony\Component\Mime\RawMessage[]
119+
* @return RawMessage[]
123120
*/
124121
public function grabSentEmails(): array
125122
{
@@ -161,6 +158,13 @@ public function getMailerEvent(int $index = 0, ?string $transport = null): ?Mess
161158
return $this->getMessageMailerEvents()->getEvents($transport)[$index] ?? null;
162159
}
163160

161+
protected function grabLastSentRawMessage(): ?RawMessage
162+
{
163+
$messages = $this->getMessageMailerEvents()->getMessages();
164+
165+
return $messages ? $messages[array_key_last($messages)] : null;
166+
}
167+
164168
protected function getMessageMailerEvents(): MessageEvents
165169
{
166170
$logger = $this->grabCachedService(

src/Codeception/Module/Symfony/MimeAssertionsTrait.php

Lines changed: 45 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -7,31 +7,32 @@
77
use PHPUnit\Framework\Assert;
88
use PHPUnit\Framework\Constraint\LogicalNot;
99
use Symfony\Component\Mime\Email;
10+
use Symfony\Component\Mime\Message;
11+
use Symfony\Component\Mime\RawMessage;
1012
use Symfony\Component\Mime\Test\Constraint as MimeConstraint;
1113

1214
use function sprintf;
1315

1416
trait MimeAssertionsTrait
1517
{
1618
/**
17-
* Verify that an email contains addresses with a [header](https://datatracker.ietf.org/doc/html/rfc4021)
19+
* Verify that a message contains addresses with a [header](https://datatracker.ietf.org/doc/html/rfc4021)
1820
* `$headerName` and its expected value `$expectedValue`.
19-
* If the Email object is not specified, the last email sent is used instead.
21+
* If no Message is specified, the last sent message is used instead.
2022
*
2123
* ```php
2224
* <?php
2325
* $I->assertEmailAddressContains('To', 'jane_doe@example.com');
2426
* ```
2527
*/
26-
public function assertEmailAddressContains(string $headerName, string $expectedValue, ?Email $email = null): void
28+
public function assertEmailAddressContains(string $headerName, string $expectedValue, ?Message $email = null): void
2729
{
28-
$email = $this->verifyEmailObject($email, __FUNCTION__);
29-
$this->assertThat($email, new MimeConstraint\EmailAddressContains($headerName, $expectedValue));
30+
$this->assertThat($this->getMessageOrFail($email, __FUNCTION__), new MimeConstraint\EmailAddressContains($headerName, $expectedValue));
3031
}
3132

3233
/**
33-
* Verify that an email has sent the specified number `$count` of attachments.
34-
* If the Email object is not specified, the last email sent is used instead.
34+
* Verify that an email has the specified number `$count` of attachments.
35+
* If no Email is specified, the last sent email is used instead.
3536
*
3637
* ```php
3738
* <?php
@@ -40,60 +41,56 @@ public function assertEmailAddressContains(string $headerName, string $expectedV
4041
*/
4142
public function assertEmailAttachmentCount(int $count, ?Email $email = null): void
4243
{
43-
$email = $this->verifyEmailObject($email, __FUNCTION__);
44-
$this->assertThat($email, new MimeConstraint\EmailAttachmentCount($count));
44+
$this->assertThat($this->getMessageOrFail($email, __FUNCTION__), new MimeConstraint\EmailAttachmentCount($count));
4545
}
4646

4747
/**
48-
* Verify that an email has a [header](https://datatracker.ietf.org/doc/html/rfc4021) `$headerName`.
49-
* If the Email object is not specified, the last email sent is used instead.
48+
* Verify that a message has a [header](https://datatracker.ietf.org/doc/html/rfc4021) `$headerName`.
49+
* If no Message is specified, the last sent message is used instead.
5050
*
5151
* ```php
5252
* <?php
5353
* $I->assertEmailHasHeader('Bcc');
5454
* ```
5555
*/
56-
public function assertEmailHasHeader(string $headerName, ?Email $email = null): void
56+
public function assertEmailHasHeader(string $headerName, ?Message $email = null): void
5757
{
58-
$email = $this->verifyEmailObject($email, __FUNCTION__);
59-
$this->assertThat($email, new MimeConstraint\EmailHasHeader($headerName));
58+
$this->assertThat($this->getMessageOrFail($email, __FUNCTION__), new MimeConstraint\EmailHasHeader($headerName));
6059
}
6160

6261
/**
6362
* Verify that the [header](https://datatracker.ietf.org/doc/html/rfc4021)
64-
* `$headerName` of an email is not the expected one `$expectedValue`.
65-
* If the Email object is not specified, the last email sent is used instead.
63+
* `$headerName` of a message is not the expected one `$expectedValue`.
64+
* If no Message is specified, the last sent message is used instead.
6665
*
6766
* ```php
6867
* <?php
6968
* $I->assertEmailHeaderNotSame('To', 'john_doe@gmail.com');
7069
* ```
7170
*/
72-
public function assertEmailHeaderNotSame(string $headerName, string $expectedValue, ?Email $email = null): void
71+
public function assertEmailHeaderNotSame(string $headerName, string $expectedValue, ?Message $email = null): void
7372
{
74-
$email = $this->verifyEmailObject($email, __FUNCTION__);
75-
$this->assertThat($email, new LogicalNot(new MimeConstraint\EmailHeaderSame($headerName, $expectedValue)));
73+
$this->assertThat($this->getMessageOrFail($email, __FUNCTION__), new LogicalNot(new MimeConstraint\EmailHeaderSame($headerName, $expectedValue)));
7674
}
7775

7876
/**
7977
* Verify that the [header](https://datatracker.ietf.org/doc/html/rfc4021)
80-
* `$headerName` of an email is the same as expected `$expectedValue`.
81-
* If the Email object is not specified, the last email sent is used instead.
78+
* `$headerName` of a message is the same as expected `$expectedValue`.
79+
* If no Message is specified, the last sent message is used instead.
8280
*
8381
* ```php
8482
* <?php
8583
* $I->assertEmailHeaderSame('To', 'jane_doe@gmail.com');
8684
* ```
8785
*/
88-
public function assertEmailHeaderSame(string $headerName, string $expectedValue, ?Email $email = null): void
86+
public function assertEmailHeaderSame(string $headerName, string $expectedValue, ?Message $email = null): void
8987
{
90-
$email = $this->verifyEmailObject($email, __FUNCTION__);
91-
$this->assertThat($email, new MimeConstraint\EmailHeaderSame($headerName, $expectedValue));
88+
$this->assertThat($this->getMessageOrFail($email, __FUNCTION__), new MimeConstraint\EmailHeaderSame($headerName, $expectedValue));
9289
}
9390

9491
/**
9592
* Verify that the HTML body of an email contains `$text`.
96-
* If the Email object is not specified, the last email sent is used instead.
93+
* If no Email is specified, the last sent email is used instead.
9794
*
9895
* ```php
9996
* <?php
@@ -102,13 +99,12 @@ public function assertEmailHeaderSame(string $headerName, string $expectedValue,
10299
*/
103100
public function assertEmailHtmlBodyContains(string $text, ?Email $email = null): void
104101
{
105-
$email = $this->verifyEmailObject($email, __FUNCTION__);
106-
$this->assertThat($email, new MimeConstraint\EmailHtmlBodyContains($text));
102+
$this->assertThat($this->getMessageOrFail($email, __FUNCTION__), new MimeConstraint\EmailHtmlBodyContains($text));
107103
}
108104

109105
/**
110106
* Verify that the HTML body of an email does not contain a text `$text`.
111-
* If the Email object is not specified, the last email sent is used instead.
107+
* If no Email is specified, the last sent email is used instead.
112108
*
113109
* ```php
114110
* <?php
@@ -117,28 +113,26 @@ public function assertEmailHtmlBodyContains(string $text, ?Email $email = null):
117113
*/
118114
public function assertEmailHtmlBodyNotContains(string $text, ?Email $email = null): void
119115
{
120-
$email = $this->verifyEmailObject($email, __FUNCTION__);
121-
$this->assertThat($email, new LogicalNot(new MimeConstraint\EmailHtmlBodyContains($text)));
116+
$this->assertThat($this->getMessageOrFail($email, __FUNCTION__), new LogicalNot(new MimeConstraint\EmailHtmlBodyContains($text)));
122117
}
123118

124119
/**
125-
* Verify that an email does not have a [header](https://datatracker.ietf.org/doc/html/rfc4021) `$headerName`.
126-
* If the Email object is not specified, the last email sent is used instead.
120+
* Verify that a message does not have a [header](https://datatracker.ietf.org/doc/html/rfc4021) `$headerName`.
121+
* If no Message is specified, the last sent message is used instead.
127122
*
128123
* ```php
129124
* <?php
130125
* $I->assertEmailNotHasHeader('Bcc');
131126
* ```
132127
*/
133-
public function assertEmailNotHasHeader(string $headerName, ?Email $email = null): void
128+
public function assertEmailNotHasHeader(string $headerName, ?Message $email = null): void
134129
{
135-
$email = $this->verifyEmailObject($email, __FUNCTION__);
136-
$this->assertThat($email, new LogicalNot(new MimeConstraint\EmailHasHeader($headerName)));
130+
$this->assertThat($this->getMessageOrFail($email, __FUNCTION__), new LogicalNot(new MimeConstraint\EmailHasHeader($headerName)));
137131
}
138132

139133
/**
140134
* Verify the text body of an email contains a `$text`.
141-
* If the Email object is not specified, the last email sent is used instead.
135+
* If no Email is specified, the last sent email is used instead.
142136
*
143137
* ```php
144138
* <?php
@@ -147,13 +141,12 @@ public function assertEmailNotHasHeader(string $headerName, ?Email $email = null
147141
*/
148142
public function assertEmailTextBodyContains(string $text, ?Email $email = null): void
149143
{
150-
$email = $this->verifyEmailObject($email, __FUNCTION__);
151-
$this->assertThat($email, new MimeConstraint\EmailTextBodyContains($text));
144+
$this->assertThat($this->getMessageOrFail($email, __FUNCTION__), new MimeConstraint\EmailTextBodyContains($text));
152145
}
153146

154147
/**
155148
* Verify that the text body of an email does not contain a `$text`.
156-
* If the Email object is not specified, the last email sent is used instead.
149+
* If no Email is specified, the last sent email is used instead.
157150
*
158151
* ```php
159152
* <?php
@@ -162,19 +155,23 @@ public function assertEmailTextBodyContains(string $text, ?Email $email = null):
162155
*/
163156
public function assertEmailTextBodyNotContains(string $text, ?Email $email = null): void
164157
{
165-
$email = $this->verifyEmailObject($email, __FUNCTION__);
166-
$this->assertThat($email, new LogicalNot(new MimeConstraint\EmailTextBodyContains($text)));
158+
$this->assertThat($this->getMessageOrFail($email, __FUNCTION__), new LogicalNot(new MimeConstraint\EmailTextBodyContains($text)));
167159
}
168160

169161
/**
170-
* Returns the last email sent if $email is null. If no email has been sent it fails.
162+
* Resolves a Message for assertion or fails the test.
163+
*
164+
* Uses the provided `$message` or retrieves the last sent message.
165+
* Fails if no message is found, or if it is a plain RawMessage lacking the headers and structure required by Mime constraints.
171166
*/
172-
private function verifyEmailObject(?Email $email, string $function): Email
167+
private function getMessageOrFail(?Message $message, string $caller): Message
173168
{
174-
$email = $email ?: $this->grabLastSentEmail();
175-
$errorMsgTemplate = "There is no email to verify. An Email object was not specified when invoking '%s' and the application has not sent one.";
176-
return $email ?? Assert::fail(
177-
sprintf($errorMsgTemplate, $function)
178-
);
169+
$message ??= $this->grabLastSentRawMessage();
170+
171+
if (!$message instanceof Message) {
172+
Assert::fail(sprintf("No message to verify for '%s'. None was provided or sent by the application.", $caller));
173+
}
174+
175+
return $message;
179176
}
180177
}

tests/MailerAssertionsTest.php

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Symfony\Component\Mailer\Event\MessageEvent;
1010
use Symfony\Component\Mime\Address;
1111
use Symfony\Component\Mime\Email;
12+
use Symfony\Component\Mime\Message;
1213
use Tests\Support\CodeceptTestCase;
1314

1415
final class MailerAssertionsTest extends CodeceptTestCase
@@ -27,6 +28,12 @@ public function testAssertEmailCount(): void
2728
$this->assertEmailCount(1);
2829
}
2930

31+
public function testAssertEmailCountWithMessage(): void
32+
{
33+
$this->client->request('GET', '/send-message');
34+
$this->assertEmailCount(1);
35+
}
36+
3037
public function testAssertEmailIsNotQueued(): void
3138
{
3239
$this->client->request('GET', '/send-email');
@@ -58,18 +65,40 @@ public function testGetMailerEvent(): void
5865
$this->assertInstanceOf(MessageEvent::class, $this->getMailerEvent());
5966
}
6067

61-
public function testGrabLastSentEmail(): void
68+
public function testGrabLastSentEmailReturnsEmailInstance(): void
6269
{
6370
$this->client->request('GET', '/send-email');
6471
$email = $this->grabLastSentEmail();
6572
$this->assertInstanceOf(Email::class, $email);
66-
$this->assertSame('jane_doe@example.com', $email->getTo()[0]->getAddress());
6773
}
6874

69-
public function testGrabSentEmails(): void
75+
public function testGrabLastSentEmailReturnsMessageInstance(): void
76+
{
77+
$this->client->request('GET', '/send-message');
78+
$message = $this->grabLastSentEmail();
79+
$this->assertInstanceOf(Message::class, $message);
80+
$this->assertNotInstanceOf(Email::class, $message);
81+
}
82+
83+
public function testGrabLastSentEmailReturnsNullWhenNoMessagesSent(): void
84+
{
85+
$this->assertNull($this->grabLastSentEmail());
86+
}
87+
88+
public function testGrabSentEmailsWithEmailType(): void
7089
{
7190
$this->client->request('GET', '/send-email');
72-
$this->assertCount(1, $this->grabSentEmails());
91+
$emails = $this->grabSentEmails();
92+
$this->assertCount(1, $emails);
93+
$this->assertInstanceOf(Email::class, $emails[0]);
94+
}
95+
96+
public function testGrabSentEmailsWithMessageType(): void
97+
{
98+
$this->client->request('GET', '/send-message');
99+
$messages = $this->grabSentEmails();
100+
$this->assertCount(1, $messages);
101+
$this->assertInstanceOf(Message::class, $messages[0]);
73102
}
74103

75104
public function testSeeEmailIsSent(): void
@@ -80,10 +109,8 @@ public function testSeeEmailIsSent(): void
80109

81110
public function testEdgeCases(): void
82111
{
83-
// No emails sent
84112
$this->assertNull($this->grabLastSentEmail());
85113

86-
// Out of range index
87114
$this->client->request('GET', '/send-email');
88115
$this->assertNull($this->getMailerEvent(999));
89116
}

0 commit comments

Comments
 (0)