Mailglass.TestAssertions (Mailglass v0.3.2)

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 at least one mail was sent in the current test process.

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 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_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_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 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.