The shipped ExUnit.CaseTemplate adopters use to test inbound mailboxes
(ITEST-05) — the inbound analog of outbound's Mailglass.MailerCase, and the
keystone that ties the inbound Testing helpers together.
use MailglassInbound.MailboxCase injects import MailglassInbound.TestAssertions
and the Fixtures/Test aliases, checks out an Ecto sandbox on the adopter's
configured repo, sets tenancy, resets the only process-global state the inbound
fixtures touch, and tears the sandbox down on exit.
Ships in lib/ (not test/support/)
Like the other inbound Testing helpers, this case template ships in the
mailglass_inbound Hex package via the files: ~w(lib …) manifest so adopters
can use it from their own suites. Because it ships in lib/ it references
ONLY core/runtime modules — never Oban, ExAws, or Plug.Test (Pitfall 6),
so it compiles cleanly under mix compile --no-optional-deps --warnings-as-errors.
The adopter's repo is resolved from app-env
The sandbox is checked out on the repo resolved from
Application.get_env(:mailglass_inbound, :repo), exactly like the
MailglassInbound.Repo facade. The case template NEVER references the
package's own test repo literal — the package does not own a repo (Pitfall 1).
Adopters configure one in config/test.exs:
config :mailglass_inbound, :repo, MyApp.RepoIf unset, setup raises with that exact instruction. (The package's own
self-tests work because config/test.exs points :repo at the support repo
under test/support.)
It snapshots NO app-env
Unlike Mailglass.MailerCase (which snapshots/restores its async-mode
application-env keys so deliver_later/2 runs inline), this case template
writes and restores no application-env key. Inbound achieves synchronous
execution structurally: MailglassInbound.Test.Ingress drives
MailglassInbound.Execution.execute/2 (SYNC) directly — there is no
async-mode app-env key to flip and therefore no leak surface across tests
(D-47-12, T-47-13). The only teardown is Sandbox.stop_owner/1; the only
shared-state hygiene is the per-setup ETS / process-dict reset below.
Default setup
Ecto.Adapters.SQL.Sandbox.start_owner!(repo, shared: not tags[:async])Mailglass.Tenancy.put_current("test-tenant")(unless@tag tenant: :unset)- resets
Mailglass.Webhook.Providers.SES.CertCache(process-global ETS) - resets
MailglassInbound.S3Fetcher.Fake(process-dict) - best-effort
Phoenix.PubSub.subscribe/2on the coreMailglass.PubSubserver (the inbound plug broadcasts there); the send-based capture fromMailglassInbound.Test.Ingressis the primary assertion path on_exit→Sandbox.stop_owner/1
Supported tags
@tag tenant: "acme"— override the default"test-tenant"@tag tenant: :unset— disable tenancy stamping@tag async: false— disable async (sandbox checks out in shared mode)
Example
defmodule MyApp.WelcomeMailboxTest do
use MailglassInbound.MailboxCase, async: false
test "accepts a welcome message" do
message = Fixtures.build_inbound_message(subject: "Welcome")
{:ok, %{outcome: %{outcome: :accept}, route: %{mailbox: MyApp.WelcomeMailbox}}} =
Test.Ingress.receive_inbound(message, routes: my_routes())
# ONE assertion per drive: each `assert_inbound_*` reads the captured
# tuple with `assert_received`, which CONSUMES it from the process
# mailbox. To run a second assertion, drive a second message (with a
# distinct `provider_message_id` so it is a fresh receive).
assert_inbound_received(subject: "Welcome")
end
end