├── .github ├── dependabot.yml ├── release-drafter.yml └── workflows │ ├── release-drafter.yml │ └── tests.yml ├── LICENSE ├── README.md ├── composer.json └── src ├── Module └── MailCatcher.php └── Util └── Email.php /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # https://help.github.com/en/github/administering-a-repository/configuration-options-for-dependency-updates#about-the-dependabotyml-file 2 | version: 2 3 | updates: 4 | - package-ecosystem: composer 5 | directory: / 6 | schedule: 7 | interval: daily 8 | commit-message: 9 | prefix: composer 10 | include: scope 11 | - package-ecosystem: github-actions 12 | directory: / 13 | schedule: 14 | interval: daily 15 | commit-message: 16 | prefix: github-actions 17 | include: scope -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | template: | 2 | ## What’s Changed 3 | 4 | $CHANGES 5 | -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | # Drafts your next Release notes as Pull Requests are merged into "master" 2 | name: Release Drafter 3 | 4 | on: 5 | push: 6 | branches: 7 | - master 8 | 9 | permissions: 10 | contents: read 11 | 12 | jobs: 13 | update_release_draft: 14 | permissions: 15 | contents: write 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: release-drafter/release-drafter@v6 19 | env: 20 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 21 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | concurrency: 12 | group: ${{ github.sha }}-tests 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | test: 17 | runs-on: ubuntu-latest 18 | strategy: 19 | matrix: 20 | php: ['7.4', '8.0', '8.1', '8.2', '8.3'] 21 | codeception: ['^4.0', '^5.0'] 22 | exclude: 23 | # Codeception 5 requires PHP ≥ 8 24 | - php: '7.4' 25 | codeception: '^5.0' 26 | 27 | steps: 28 | - uses: actions/checkout@v4 29 | - name: Setup Ruby 30 | uses: ruby/setup-ruby@v1 31 | with: 32 | ruby-version: 3.1.1 33 | - run: gem install mime-types --version "< 3" 34 | - run: gem install --conservative mailcatcher 35 | - run: mailcatcher 36 | # Temporary workaround for https://github.com/sj26/mailcatcher/issues/182 37 | - run: mailcatcher -f > out.txt& 38 | - name: Setup PHP 39 | uses: shivammathur/setup-php@v2 40 | with: 41 | coverage: xdebug 42 | ini-values: "sendmail_path = /usr/bin/env catchmail -f some@from.address" 43 | php-version: ${{ matrix.php }} 44 | - uses: ramsey/composer-install@v3 45 | with: 46 | dependency-versions: highest 47 | composer-options: "--with=codeception/codeception:${{ matrix.codeception }}" 48 | - run: php vendor/bin/codecept build 49 | - name: Acceptance tests 50 | run: php vendor/bin/codecept run acceptance -d 51 | - name: Unit tests 52 | env: 53 | XDEBUG_MODE: coverage 54 | run: php vendor/bin/codecept run unit --coverage-xml -d 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Jordan Eldredge and contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Codeception MailCatcher Module 2 | 3 | [![Build Status](https://travis-ci.org/captbaritone/codeception-mailcatcher-module.svg)](https://travis-ci.org/captbaritone/codeception-mailcatcher-module) 4 | 5 | This module will let you test emails that are sent during your Codeception 6 | acceptance tests. It depends upon you having 7 | [MailCatcher](http://mailcatcher.me/) installed on your development server. 8 | 9 | It was inspired by the Codeception blog post: [Testing Email in 10 | PHP](http://codeception.com/12-15-2013/testing-emails-in-php). It is currently 11 | very simple. Send a pull request or file an issue if you have ideas for more 12 | features. 13 | 14 | ## Installation 15 | 16 | 1. Add the package to your `composer.json`: 17 | 18 | `composer require --dev captbaritone/mailcatcher-codeception-module` 19 | 20 | 2. Configure your project to actually send emails through `smtp://127.0.0.1:1025` in the test environment 21 | 22 | 3. Enable the module in your `acceptance.suite.yml`: 23 | ```yaml 24 | modules: 25 | enabled: 26 | - MailCatcher 27 | config: 28 | MailCatcher: 29 | url: 'http://127.0.0.1' 30 | port: '1080' 31 | ``` 32 | 33 | ## Optional Configuration 34 | 35 | If you need to specify some special options (e.g. SSL verification or authentication 36 | headers), you can set all of the allowed [Guzzle request options](https://guzzle.readthedocs.org/en/5.3/clients.html#request-options): 37 | 38 | class_name: WebGuy 39 | modules: 40 | enabled: 41 | - MailCatcher 42 | config: 43 | MailCatcher: 44 | url: 'http://127.0.0.1' 45 | port: '1080' 46 | guzzleRequestOptions: 47 | verify: false 48 | debug: true 49 | version: 1.0 50 | 51 | ## Example Usage 52 | ```php 53 | wantTo('Get a password reset email'); 56 | 57 | // Clear old emails from MailCatcher 58 | $I->resetEmails(); 59 | 60 | // Reset password 61 | $I->amOnPage('forgotPassword.php'); 62 | $I->fillField("input[name='email']", 'user@example.com'); 63 | $I->click('Submit'); 64 | $I->see('Please check your inbox'); 65 | 66 | $I->seeInLastEmail('Please click this link to reset your password'); 67 | ``` 68 | 69 | ## Actions 70 | 71 | ### resetEmails 72 | 73 | Clears the emails in MailCatcher's list. This prevents seeing emails sent 74 | during a previous test. You probably want to do this before you trigger any 75 | emails to be sent 76 | 77 | Example: 78 | 79 | resetEmails(); 82 | ?> 83 | 84 | ### seeEmailAttachmentCount 85 | 86 | Checks expected count of attachments in last email. 87 | 88 | Example: 89 | 90 | seeEmailAttachmentCount(1); 92 | ?> 93 | 94 | * Param $expectCount 95 | 96 | ### seeAttachmentInLastEmail 97 | 98 | Checks that last email contains an attachment with filename. 99 | 100 | Example: 101 | 102 | seeAttachmentInLastEmail('image.jpg'); 104 | ?> 105 | 106 | * Param $filename 107 | 108 | ### seeInLastEmail 109 | 110 | Checks that an email contains a value. It searches the full raw text of the 111 | email: headers, subject line, and body. 112 | 113 | Example: 114 | 115 | seeInLastEmail('Thanks for signing up!'); 117 | ?> 118 | 119 | * Param $text 120 | 121 | ### seeInLastEmailTo 122 | 123 | Checks that the last email sent to an address contains a value. It searches the 124 | full raw text of the email: headers, subject line, and body. 125 | 126 | This is useful if, for example a page triggers both an email to the new user, 127 | and to the administrator. 128 | 129 | Example: 130 | 131 | seeInLastEmailTo('user@example.com', 'Thanks for signing up!'); 133 | $I->seeInLastEmailTo('admin@example.com', 'A new user has signed up!'); 134 | ?> 135 | 136 | * Param $email 137 | * Param $text 138 | 139 | ### dontSeeInLastEmail 140 | 141 | Checks that an email does NOT contain a value. It searches the full raw text of the 142 | email: headers, subject line, and body. 143 | 144 | Example: 145 | 146 | dontSeeInLastEmail('Hit me with those laser beams'); 148 | ?> 149 | 150 | * Param $text 151 | 152 | ### dontSeeInLastEmailTo 153 | 154 | Checks that the last email sent to an address does NOT contain a value. It searches the 155 | full raw text of the email: headers, subject line, and body. 156 | 157 | Example: 158 | 159 | dontSeeInLastEmailTo('admin@example.com', 'But shoot it in the right direction'); 161 | ?> 162 | 163 | * Param $email 164 | * Param $text 165 | 166 | ### grabAttachmentsFromLastEmail 167 | 168 | Grab Attachments From Email 169 | 170 | Returns array with the format [ [filename1 => bytes1], [filename2 => bytes2], ...] 171 | 172 | Example: 173 | 174 | grabAttachmentsFromLastEmail(); 176 | ?> 177 | 178 | ### grabMatchesFromLastEmail 179 | 180 | Extracts an array of matches and sub-matches from the last email based on 181 | a regular expression. It searches the full raw text of the email: headers, 182 | subject line, and body. The return value is an array like that returned by 183 | `preg_match()`. 184 | 185 | Example: 186 | 187 | grabMatchesFromLastEmail('@(.*)@'); 189 | ?> 190 | 191 | * Param $regex 192 | 193 | ### grabFromLastEmail 194 | 195 | Extracts a string from the last email based on a regular expression. 196 | It searches the full raw text of the email: headers, subject line, and body. 197 | 198 | Example: 199 | 200 | grabFromLastEmail('@(.*)@'); 202 | ?> 203 | 204 | * Param $regex 205 | 206 | ### grabUrlsFromLastEmail 207 | 208 | Extracts an array of urls from the last email. 209 | It searches the full raw body of the email. 210 | The return value is an array of strings. 211 | 212 | Example: 213 | 214 | grabUrlsFromLastEmail(); 216 | ?> 217 | 218 | ### lastMessageFrom 219 | 220 | Grab the full email object sent to an address. 221 | 222 | Example: 223 | 224 | lastMessageFrom('example@example.com'); 226 | $I->assertNotEmpty($email['attachments']); 227 | ?> 228 | 229 | ### lastMessage 230 | 231 | Grab the full email object from the last email. 232 | 233 | Example: 234 | 235 | grabLastEmail(); 237 | $I->assertNotEmpty($email['attachments']); 238 | ?> 239 | 240 | ### grabMatchesFromLastEmailTo 241 | 242 | Extracts an array of matches and sub-matches from the last email to a given 243 | address based on a regular expression. It searches the full raw text of the 244 | email: headers, subject line, and body. The return value is an array like that 245 | returned by `preg_match()`. 246 | 247 | Example: 248 | 249 | grabMatchesFromLastEmailTo('user@example.com', '@(.*)@'); 251 | ?> 252 | 253 | * Param $email 254 | * Param $regex 255 | 256 | ### grabFromLastEmailTo 257 | 258 | Extracts a string from the last email to a given address based on a regular 259 | expression. It searches the full raw text of the email: headers, subject 260 | line, and body. 261 | 262 | Example: 263 | 264 | grabFromLastEmailTo('user@example.com', '@(.*)@'); 266 | ?> 267 | 268 | * Param $email 269 | * Param $regex 270 | 271 | ### seeEmailCount 272 | 273 | Asserts that a certain number of emails have been sent since the last time 274 | `resetEmails()` was called. 275 | 276 | Example: 277 | 278 | seeEmailCount(2); 280 | ?> 281 | 282 | * Param $count 283 | 284 | # License 285 | 286 | Released under the same license as Codeception: [MIT](https://github.com/captbaritone/codeception-mailcatcher-module/blob/master/LICENSE) 287 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "captbaritone/mailcatcher-codeception-module", 3 | "description": "Test emails in your Codeception acceptance tests", 4 | "license": "MIT", 5 | "authors": [ 6 | { 7 | "name": "Jordan Eldredge", 8 | "email": "jordan@jordaneldredge.com" 9 | }, 10 | { 11 | "name": "James King", 12 | "email": "james@jamesking.dev" 13 | } 14 | ], 15 | "require": { 16 | "php": "^7.4 || ^8.0", 17 | "ext-json": "*", 18 | "guzzlehttp/guzzle": "^6.0 || ^7.0", 19 | "codeception/codeception": "^4.0 || ^5.0", 20 | "zbateson/mail-mime-parser": "^1.2" 21 | }, 22 | "autoload": { 23 | "psr-4": { 24 | "Codeception\\": "src" 25 | } 26 | }, 27 | "require-dev": { 28 | "codeception/module-asserts": "^1.1 || ^2", 29 | "phpmailer/phpmailer": "^6.1.6" 30 | }, 31 | "suggest": { 32 | "codeception/module-asserts": "Required if using Codeception >= 4.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Module/MailCatcher.php: -------------------------------------------------------------------------------- 1 | config['url'], '/') . ':' . $this->config['port']; 56 | 57 | $guzzleConfig = [ 58 | 'base_uri' => $base_uri 59 | ]; 60 | if (isset($this->config['guzzleRequestOptions'])) { 61 | $guzzleConfig = array_merge($guzzleConfig, $this->config['guzzleRequestOptions']); 62 | } 63 | 64 | $this->mailcatcher = new Client($guzzleConfig); 65 | } 66 | 67 | 68 | /** 69 | * Reset emails 70 | * 71 | * Clear all emails from mailcatcher. You probably want to do this before 72 | * you do the thing that will send emails 73 | * 74 | * @author Jordan Eldredge 75 | **/ 76 | public function resetEmails(): void 77 | { 78 | $this->mailcatcher->delete('/messages'); 79 | } 80 | 81 | 82 | /** 83 | * See In Last Email 84 | * 85 | * Look for a string in the most recent email 86 | * 87 | * @author Jordan Eldredge 88 | **/ 89 | public function seeInLastEmail(string $expected): void 90 | { 91 | $email = $this->lastMessage(); 92 | $this->seeInEmail($email, $expected); 93 | } 94 | 95 | /** 96 | * See In Last Email subject 97 | * 98 | * Look for a string in the most recent email subject 99 | * 100 | * @author Antoine Augusti 101 | **/ 102 | public function seeInLastEmailSubject(string $expected): void 103 | { 104 | $email = $this->lastMessage(); 105 | $this->seeInEmailSubject($email, $expected); 106 | } 107 | 108 | /** 109 | * Don't See In Last Email subject 110 | * 111 | * Look for the absence of a string in the most recent email subject 112 | **/ 113 | public function dontSeeInLastEmailSubject(string $expected): void 114 | { 115 | $email = $this->lastMessage(); 116 | $this->dontSeeInEmailSubject($email, $expected); 117 | } 118 | 119 | /** 120 | * Don't See In Last Email 121 | * 122 | * Look for the absence of a string in the most recent email 123 | **/ 124 | public function dontSeeInLastEmail(string $unexpected): void 125 | { 126 | $email = $this->lastMessage(); 127 | $this->dontSeeInEmail($email, $unexpected); 128 | } 129 | 130 | /** 131 | * See In Last Email To 132 | * 133 | * Look for a string in the most recent email sent to $address 134 | * 135 | * @author Jordan Eldredge 136 | **/ 137 | public function seeInLastEmailTo(string $address, string $expected): void 138 | { 139 | $email = $this->lastMessageTo($address); 140 | $this->seeInEmail($email, $expected); 141 | } 142 | 143 | /** 144 | * Don't See In Last Email To 145 | * 146 | * Look for the absence of a string in the most recent email sent to $address 147 | **/ 148 | public function dontSeeInLastEmailTo(string $address, string $unexpected): void 149 | { 150 | $email = $this->lastMessageTo($address); 151 | $this->dontSeeInEmail($email, $unexpected); 152 | } 153 | 154 | /** 155 | * See In Last Email Subject To 156 | * 157 | * Look for a string in the most recent email subject sent to $address 158 | * 159 | * @author Antoine Augusti 160 | **/ 161 | public function seeInLastEmailSubjectTo(string $address, string $expected): void 162 | { 163 | $email = $this->lastMessageTo($address); 164 | $this->seeInEmailSubject($email, $expected); 165 | } 166 | 167 | /** 168 | * Don't See In Last Email Subject To 169 | * 170 | * Look for the absence of a string in the most recent email subject sent to $address 171 | **/ 172 | public function dontSeeInLastEmailSubjectTo(string $address, string $unexpected): void 173 | { 174 | $email = $this->lastMessageTo($address); 175 | $this->dontSeeInEmailSubject($email, $unexpected); 176 | } 177 | 178 | public function lastMessage(): \Codeception\Util\Email 179 | { 180 | $messages = $this->messages(); 181 | if (empty($messages)) { 182 | $this->fail("No messages received"); 183 | } 184 | 185 | $last = array_shift($messages); 186 | 187 | return $this->emailFromId($last['id']); 188 | } 189 | 190 | public function lastMessageTo(string $address): \Codeception\Util\Email 191 | { 192 | $ids = []; 193 | $messages = $this->messages(); 194 | if (empty($messages)) { 195 | $this->fail("No messages received"); 196 | } 197 | 198 | foreach ($messages as $message) { 199 | foreach ($message['recipients'] as $recipient) { 200 | if (strpos($recipient, $address) !== false) { 201 | $ids[] = $message['id']; 202 | } 203 | } 204 | } 205 | 206 | if (count($ids) === 0) { 207 | $this->fail("No messages sent to {$address}"); 208 | } 209 | 210 | return $this->emailFromId(max($ids)); 211 | } 212 | 213 | public function lastMessageFrom(string $address): \Codeception\Util\Email 214 | { 215 | $ids = []; 216 | $messages = $this->messages(); 217 | if (empty($messages)) { 218 | $this->fail("No messages received"); 219 | } 220 | 221 | foreach ($messages as $message) { 222 | if (strpos($message['sender'], $address) !== false) { 223 | $ids[] = $message['id']; 224 | } 225 | 226 | // @todo deprecated, remove 227 | foreach ($message['recipients'] as $recipient) { 228 | if (strpos($recipient, $address) !== false) { 229 | trigger_error('`lastMessageFrom` no longer accepts a recipient email.', E_USER_DEPRECATED); 230 | $ids[] = $message['id']; 231 | } 232 | } 233 | } 234 | 235 | if (count($ids) === 0) { 236 | $this->fail("No messages sent from {$address}"); 237 | } 238 | 239 | return $this->emailFromId(max($ids)); 240 | } 241 | 242 | /** 243 | * Grab Matches From Last Email 244 | * 245 | * Look for a regex in the email source and return it's matches 246 | * 247 | * @author Stephan Hochhaus 248 | * @return mixed[] 249 | **/ 250 | public function grabMatchesFromLastEmail(string $regex): array 251 | { 252 | $email = $this->lastMessage(); 253 | return $this->grabMatchesFromEmail($email, $regex); 254 | } 255 | 256 | /** 257 | * Grab From Last Email 258 | * 259 | * Look for a regex in the email source and return it 260 | * 261 | * @author Stephan Hochhaus 262 | **/ 263 | public function grabFromLastEmail(string $regex): string 264 | { 265 | $matches = $this->grabMatchesFromLastEmail($regex); 266 | return $matches[0]; 267 | } 268 | 269 | /** 270 | * Grab Matches From Last Email To 271 | * 272 | * Look for a regex in most recent email sent to $addres email source and 273 | * return it's matches 274 | * 275 | * @author Stephan Hochhaus 276 | * @return mixed[] 277 | **/ 278 | public function grabMatchesFromLastEmailTo(string $address, string $regex): array 279 | { 280 | $email = $this->lastMessageTo($address); 281 | return $this->grabMatchesFromEmail($email, $regex); 282 | } 283 | 284 | /** 285 | * Grab From Last Email To 286 | * 287 | * Look for a regex in most recent email sent to $addres email source and 288 | * return it 289 | * 290 | * @author Stephan Hochhaus 291 | **/ 292 | public function grabFromLastEmailTo(string $address, string $regex): string 293 | { 294 | $matches = $this->grabMatchesFromLastEmailTo($address, $regex); 295 | return $matches[0]; 296 | } 297 | 298 | /** 299 | * Grab Urls From Email 300 | * 301 | * Return the urls the email contains 302 | * 303 | * @author Marcelo Briones 304 | * @return mixed[] 305 | */ 306 | public function grabUrlsFromLastEmail(): array 307 | { 308 | $regex = '#\bhttps?://[^,\s()<>]+(?:\([\w\d]+\)|([^,[:punct:]\s]|/))#'; 309 | $email = $this->lastMessage(); 310 | 311 | $message = Message::from($email->getSource()); 312 | 313 | $text = $message->getTextContent(); 314 | preg_match_all($regex, $text, $text_matches); 315 | 316 | $html = $message->getHtmlContent(); 317 | preg_match_all($regex, $html, $html_matches); 318 | 319 | return array_merge($text_matches[0], $html_matches[0]); 320 | } 321 | 322 | /** 323 | * Grab Attachments From Email 324 | * 325 | * Returns array with the format [ [filename1 => bytes1], [filename2 => bytes2], ...] 326 | * 327 | * @return array 328 | * @author Marcelo Briones 329 | */ 330 | public function grabAttachmentsFromLastEmail(): array 331 | { 332 | $email = $this->lastMessage(); 333 | 334 | $message = Message::from($email->getSource()); 335 | 336 | $attachments = []; 337 | 338 | foreach ($message->getAllAttachmentParts() as $attachmentPart) { 339 | $filename = $attachmentPart->getFilename(); 340 | $content = $attachmentPart->getContent(); 341 | $attachments[$filename] = $content; 342 | } 343 | 344 | return $attachments; 345 | } 346 | 347 | /** 348 | * See Attachment In Last Email 349 | * 350 | * Look for a attachement with certain filename in the most recent email 351 | * 352 | * @author Marcelo Briones 353 | **/ 354 | public function seeAttachmentInLastEmail(string $expectedFilename): void 355 | { 356 | $email = $this->lastMessage(); 357 | $message = Message::from($email->getSource()); 358 | 359 | foreach ($message->getAllAttachmentParts() as $attachmentPart) { 360 | if ($attachmentPart->getFilename() === $expectedFilename) { 361 | return; 362 | } 363 | } 364 | $this->fail("Filename not found in attachments."); 365 | } 366 | 367 | /** 368 | * Test email count equals expected value 369 | * 370 | * @author Mike Crowe 371 | **/ 372 | public function seeEmailCount(int $expected): void 373 | { 374 | $messages = $this->messages(); 375 | $count = count($messages); 376 | $this->assertEquals($expected, $count); 377 | } 378 | 379 | /** 380 | * Checks expected count of attachment in last email. 381 | * 382 | * @author Marcelo Briones 383 | **/ 384 | public function seeEmailAttachmentCount(int $expectedCount): void 385 | { 386 | $email = $this->lastMessage(); 387 | $message = Message::from($email->getSource()); 388 | $this->assertEquals($expectedCount, $message->getAttachmentCount()); 389 | } 390 | 391 | // ----------- HELPER METHODS BELOW HERE -----------------------// 392 | /** 393 | * Messages 394 | * 395 | * Get an array of all the message objects 396 | * 397 | * @author Jordan Eldredge 398 | **/ 399 | protected function messages(): array 400 | { 401 | $response = $this->mailcatcher->get('/messages'); 402 | $messages = json_decode($response->getBody(), true); 403 | // Ensure messages are shown in the order they were recieved 404 | // https://github.com/sj26/mailcatcher/pull/184 405 | usort($messages, function ($messageA, $messageB): int { 406 | $sortKeyA = $messageA['created_at'] . $messageA['id']; 407 | $sortKeyB = $messageB['created_at'] . $messageB['id']; 408 | return ($sortKeyA > $sortKeyB) ? -1 : 1; 409 | }); 410 | return $messages; 411 | } 412 | 413 | /** 414 | * @param int|string $id 415 | */ 416 | protected function emailFromId($id): \Codeception\Util\Email 417 | { 418 | $response = $this->mailcatcher->get("/messages/{$id}.json"); 419 | $plainMessage = $this->mailcatcher->get("/messages/{$id}.source"); 420 | $messageData = json_decode($response->getBody(), true); 421 | $messageData['source'] = $plainMessage->getBody()->getContents(); 422 | 423 | return Email::createFromMailcatcherData($messageData); 424 | } 425 | 426 | protected function seeInEmailSubject(Email $email, string $expected): void 427 | { 428 | if(method_exists($this, 'assertStringContainsString')){ 429 | $this->assertStringContainsString($expected, $email->getSubject(), "Email Subject Contains"); 430 | }else{ 431 | $this->assertContains($expected, $email->getSubject(), "Email Subject Contains"); 432 | } 433 | } 434 | 435 | protected function dontSeeInEmailSubject(Email $email, string $unexpected): void 436 | { 437 | if(method_exists($this, 'assertStringContainsString')){ 438 | $this->assertStringNotContainsString($unexpected, $email->getSubject(), "Email Subject Does Not Contain"); 439 | }else{ 440 | $this->assertNotContains($unexpected, $email->getSubject(), "Email Subject Does Not Contain"); 441 | } 442 | } 443 | 444 | protected function seeInEmail(Email $email, string $expected): void 445 | { 446 | if(method_exists($this, 'assertStringContainsString')){ 447 | $this->assertStringContainsString($expected, $email->getSourceQuotedPrintableDecoded(), "Email Contains"); 448 | }else{ 449 | $this->assertContains($expected, $email->getSourceQuotedPrintableDecoded(), "Email Contains"); 450 | } 451 | } 452 | 453 | protected function dontSeeInEmail(Email $email, string $unexpected): void 454 | { 455 | if(method_exists($this, 'assertStringContainsString')){ 456 | $this->assertStringNotContainsString($unexpected, $email->getSourceQuotedPrintableDecoded(), "Email Does Not Contain"); 457 | }else{ 458 | $this->assertNotContains($unexpected, $email->getSourceQuotedPrintableDecoded(), "Email Does Not Contain"); 459 | } 460 | } 461 | 462 | protected function grabMatchesFromEmail(Email $email, string $regex): array 463 | { 464 | preg_match($regex, $email->getSourceQuotedPrintableDecoded(), $matches); 465 | $this->assertNotEmpty($matches, "No matches found for $regex"); 466 | return $matches; 467 | } 468 | } 469 | -------------------------------------------------------------------------------- /src/Util/Email.php: -------------------------------------------------------------------------------- 1 | id = $id; 33 | $this->recipients = $recipients; 34 | $this->subject = $subject; 35 | $this->source = $source; 36 | } 37 | 38 | public function getId(): int 39 | { 40 | return $this->id; 41 | } 42 | 43 | /** 44 | * @return string[] 45 | */ 46 | public function getRecipients(): array 47 | { 48 | return $this->recipients; 49 | } 50 | 51 | public function getSubject(): string 52 | { 53 | return $this->subject; 54 | } 55 | 56 | public function getSource(): string 57 | { 58 | return $this->source; 59 | } 60 | 61 | public function getSourceQuotedPrintableDecoded(): string 62 | { 63 | return quoted_printable_decode($this->source); 64 | } 65 | 66 | public static function createFromMailcatcherData(array $data): \Codeception\Util\Email 67 | { 68 | return new self($data['id'], $data['recipients'], $data['subject'], $data['source']); 69 | } 70 | } --------------------------------------------------------------------------------