Mailglass.TestAssertions (Mailglass v1.0.0)

Copy Markdown View Source

Test assertions extending Swoosh.TestAssertions (TEST-01, D-05).

Lives in lib/ (not test/support/) because it's exported for adopter consumption. Adopters import Mailglass.TestAssertions in their test helpers or use Mailglass.MailerCase which imports it.

Matcher styles

# 1. Presence (bare call)
assert_mail_sent()

# 2. Keyword match (Swoosh familiarity)
assert_mail_sent(subject: "Welcome", to: "user@example.com")

# 3. Struct pattern (macro — no explicit quoting)
assert_mail_sent(%{mailable: MyApp.UserMailer})

# 4. Predicate fn
assert_mail_sent(fn msg -> msg.stream == :transactional end)

Supported keyword matcher keys

:subject, :to, :mailable, :stream, :tenant — extensible in future versions. Any other key raises ExUnit.AssertionError.

Process-local + PubSub-backed assertions

Async-safe

Process mailbox + ETS-per-owner (Fake) + PubSub subscription (all cleaned up on process exit) means async: true tests see only their own mail. Use Mailglass.MailerCase for the canonical setup.

PII policy (T-3-06-01)

Failure messages embed caller-supplied values (e.g. subject or to address) because adopter test failures need that context. These values appear only in the adopter's own test output — not in telemetry, log streams, or cross-tenant surfaces.

Summary

Functions

Blocks until a {:delivery_updated, _, :bounced, _} broadcast arrives. See assert_mail_delivered/2 for usage notes.

Blocks until a {:delivery_updated, _, :delivered, _} broadcast arrives for the given delivery id (or %Delivery{}). Flunks on timeout.

Asserts that the HTML body of a message matches a binary or regex pattern.

Asserts that at least one mail was sent in the current test process.

Asserts that the text body of a message matches a binary or regex pattern.

Asserts that no mail was sent in the current test process.

Returns the most recent %Mailglass.Message{} sent from the current owner's Fake ETS bucket; or nil if none.

Blocks until a {:mail, %Message{}} arrives or timeout elapses. Returns the message. Flunks on timeout with a descriptive failure message.

Functions

assert_mail_bounced(delivery_or_id, timeout \\ 100)

(since 0.1.0)
@spec assert_mail_bounced(Mailglass.Outbound.Delivery.t() | binary(), timeout()) ::
  :ok

Blocks until a {:delivery_updated, _, :bounced, _} broadcast arrives. See assert_mail_delivered/2 for usage notes.

assert_mail_delivered(delivery_or_id, timeout \\ 100)

(since 0.1.0)
@spec assert_mail_delivered(Mailglass.Outbound.Delivery.t() | binary(), timeout()) ::
  :ok

Blocks until a {:delivery_updated, _, :delivered, _} broadcast arrives for the given delivery id (or %Delivery{}). Flunks on timeout.

The current test process MUST be subscribed to either Mailglass.PubSub.Topics.events(tenant_id) or Mailglass.PubSub.Topics.events(tenant_id, delivery_id) before this assertion runs. Mailglass.MailerCase handles the tenant-wide subscription in setup.

Accepts

  • delivery_id :: binary() — the UUID string
  • delivery :: %Mailglass.Outbound.Delivery{} — the struct (.id extracted)

assert_mail_html_matches(message, pattern)

(since 0.4.0)
@spec assert_mail_html_matches(Mailglass.Message.t(), String.t() | Regex.t()) :: :ok

Asserts that the HTML body of a message matches a binary or regex pattern.

assert_mail_sent()

(since 0.1.0) (macro)

Asserts that at least one mail was sent in the current test process.

Four matcher styles supported; see module doc.

Style 1: presence (bare call)

assert_mail_sent()

Style 2: keyword list

assert_mail_sent(subject: "Welcome", to: "user@example.com")
assert_mail_sent(mailable: MyApp.UserMailer, stream: :transactional)

Supported keys: :subject, :to, :mailable, :stream, :tenant

Style 3: struct pattern (no quoting needed)

assert_mail_sent(%{mailable: MyApp.UserMailer})

Style 4: predicate function

assert_mail_sent(fn msg -> msg.stream == :transactional end)

assert_mail_sent(pattern)

(since 0.1.0) (macro)

assert_mail_text_matches(message, pattern)

(since 0.4.0)
@spec assert_mail_text_matches(Mailglass.Message.t(), String.t() | Regex.t()) :: :ok

Asserts that the text body of a message matches a binary or regex pattern.

assert_no_mail_sent()

(since 0.1.0) (macro)

Asserts that no mail was sent in the current test process.

Reads the current process mailbox. Flunks if any {:mail, _} message is present.

last_mail()

(since 0.1.0)
@spec last_mail() :: Mailglass.Message.t() | nil

Returns the most recent %Mailglass.Message{} sent from the current owner's Fake ETS bucket; or nil if none.

Reads directly from the Fake ETS table — no process mailbox consumed. The returned message is still present in the mailbox for subsequent assert_mail_sent/0,1 calls.

wait_for_mail(timeout \\ 100)

(since 0.1.0)
@spec wait_for_mail(timeout()) :: Mailglass.Message.t()

Blocks until a {:mail, %Message{}} arrives or timeout elapses. Returns the message. Flunks on timeout with a descriptive failure message.

Unlike assert_mail_sent/0 (which uses assert_received and checks the mailbox synchronously), wait_for_mail/1 blocks the test for up to timeout milliseconds — useful when the delivery may arrive slightly after the assertion site.