Skip to content

Add nth email, sender and recipient assertions - #130

Merged
Jamesking56 merged 3 commits into
captbaritone:masterfrom
wazum:feature/nth-email
Oct 1, 2026
Merged

Jamesking56 merged 3 commits into
captbaritone:masterfrom
wazum:feature/nth-email

Conversation

@wazum

@wazum wazum commented Sep 30, 2026

Copy link
Copy Markdown
Contributor

Rebased and reworked version of #102, which was closed as stale.

@Jamesking56 as suggested in #102, here is a fresh PR against current master.

Resolves: #12, #98

What's new

  • Every "last email" action has an "nth email" counterpart, e.g. seeInNthEmail($nth, $text), seeInNthEmailTo($nth, $address, $text), grabUrlsFromNthEmail($nth), seeNthEmailAttachmentCount($nth, $count), nthMessage($nth) and nthMessageTo($nth, $address). Positions start at 1 and count in the order the emails were received. The ...To variants only count emails sent to that address.
  • seeInLastEmailSender, seeInNthEmailSender, seeInLastEmailRecipient and seeInNthEmailRecipient. A recipient check passes when any one recipient matches.
  • Email::getSender(). The sender is an optional last constructor argument, so existing new Email(...) calls keep working.
  • The README documents all new actions.

Fix

The sort tie-breaker from #13 compared created_at . id as a string, which puts id 10 before id 9 when both arrive in the same second. That was mostly harmless for "last email" but breaks positional lookups. It now compares [created_at, id] tuples.

Changes compared to #102

  • Dropped nthMessageFrom. It counted in the opposite direction from nthMessage and no action used it.
  • The recipient check no longer requires every recipient to match.
  • nthMessageTo no longer counts an email twice when several of its recipients match.
  • The unit tests now exercise the module's real selection logic. Before, a copy of that logic lived in the test double.
  • The last and nth variants share the attachment and URL code.
  • The old lastMessageFrom unit test never ran because its name lacked the test prefix. It is now a real test.

Tests

Unit and acceptance suites pass on PHP 8.5 with the latest dependencies and on PHP 8.3 with --prefer-lowest (Codeception 5.0.0, PHPUnit 9.6.0, mail-mime-parser 3.0.1): 84 tests, 132 assertions.

Every "last email" action gets an "nth email" counterpart. Positions
start at 1 and count in the order the emails were received; the ...To
variants only count emails sent to the given address.

Add seeInLastEmailSender, seeInNthEmailSender, seeInLastEmailRecipient
and seeInNthEmailRecipient, backed by Email::getSender(). The sender is
an optional trailing constructor argument to keep existing callers working.

Fix the message sort tie-breaker, which compared created_at and id as a
concatenated string and therefore ordered id 10 before id 9 within the
same second.

Supersedes captbaritone#102.

Resolves: captbaritone#12, captbaritone#98
@Jamesking56
Jamesking56 self-requested a review September 30, 2026 11:20

@Jamesking56 Jamesking56 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please fix the recipient assertion so it passes only when an individual recipient contains the expected value. I confirmed the false positive in both the last and nth variants with a focused PHP reproduction.

Comment thread src/Module/MailCatcher.php Outdated

protected function seeInEmailRecipients(Email $email, string $expected): void
{
$this->assertStringContainsString($expected, implode(', ', $email->getRecipients()), "Email Recipients Contain");

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Medium — check each recipient separately. Joining the recipients allows a match to span recipient boundaries. For example, with ['<alice@example.com>', '<bob@example.com>'], an expectation of '<alice@example.com>, <bob@example.com>' passes even though neither recipient contains that string. This violates the documented ‘one recipient matches’ behavior and affects both seeInLastEmailRecipient() and seeInNthEmailRecipient(). A focused PHP reproduction confirmed that both assertions incorrectly pass. Please check each recipient separately and pass only when at least one contains the expected value. Add a regression test for an expectation that spans recipient boundaries.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, thanks. Fixed in e54a249: seeInEmailRecipients now checks each recipient on its own and passes only if at least one contains the expected value. On failure the message still lists all recipients.

Regression tests:

  • Unit: testSeeInLastEmailRecipientDoesNotMatchAcrossRecipients uses your '<alice@example.com>, <bob@example.com>' example. It failed on the old code.
  • Acceptance: test_see_in_email_recipient_checks_each_recipient sends one real email to two recipients. It checks that a single recipient matches and that a value spanning both fails through seeInNthEmailRecipient.

1019fa4 adds a test that nth positions follow created_at before the id tie-breaker. It also notes in the README that addresses match by substring, the same way the existing ...To actions already work.

wazum added 2 commits October 1, 2026 11:07
Joining the recipients into one string let an expectation match across
recipient boundaries, so seeInLastEmailRecipient and
seeInNthEmailRecipient passed for values no single recipient contains.
Test that nth positions follow created_at before the id tie-breaker,
including the first and last position. Document that ...To addresses
match by substring, as the existing ...To actions always have.

@Jamesking56 Jamesking56 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed all changed PHP files and checked the README against the implementation. No actionable findings. The local unit suite passed with 31 tests and 60 assertions; all six CI test jobs passed.

@Jamesking56
Jamesking56 merged commit a8ac2bb into captbaritone:master Oct 1, 2026
8 checks passed
@Jamesking56

Copy link
Copy Markdown
Collaborator

Thanks @wazum

wazum added a commit to wazum/codeception-mailcatcher-module that referenced this pull request Oct 1, 2026
mail-mime-parser returns null for a missing text or HTML part, which used
to be passed to preg_match_all() and raised a deprecation notice. The
null guard landed with captbaritone#130; this test keeps it from regressing.

Resolves: captbaritone#138
@github-actions

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown

Released in 4.1.0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

more than just last email

2 participants