Guides
Testing
Code that sends email should be easy to test. MailingKit's memory provider records every message instead of sending it, so tests stay fast, offline and deterministic.
The memory provider
from mailingkit import MailingKit
from mailingkit.providers import MemoryProvider
async def test_signup_sends_welcome_email():
outbox = MemoryProvider()
mail = MailingKit(outbox, default_sender="app@example.com", templates="templates")
await signup(mail, name="Ada", email="ada@example.com")
assert outbox.last.subject == "Welcome to Acme, Ada"
assert "Open your workspace" in outbox.last.text
assert len(outbox.sent_to("ada@example.com")) == 1| Member | What it gives you |
|---|---|
outbox | Every message sent, in order, as EmailMessage objects. |
last | The most recent message. Raises LookupError if none. |
sent_to(email) | Messages where the address is a to, cc or bcc recipient. |
idempotency_keys | The key passed with each send. |
fail_next(*errors) | Raise these errors on the next sends, in order. |
clear() | Reset everything between tests. |
A pytest fixture
import pytest
from mailingkit import MailingKit, RetryPolicy
from mailingkit.providers import MemoryProvider
@pytest.fixture
def outbox():
return MemoryProvider()
@pytest.fixture
def mail(outbox):
return MailingKit(
outbox,
default_sender="app@example.com",
templates="templates",
retry=RetryPolicy(initial_delay=0, jitter=False), # retries without waiting
)Inject the client into your code (a function argument, a FastAPI dependency, a Django setting) rather than importing a global, and tests can swap it freely.
Testing failure paths
from mailingkit import AuthenticationError, TemporaryProviderError
async def test_transient_failure_is_retried(mail, outbox):
outbox.fail_next(TemporaryProviderError("timeout"))
result = await mail.send(to="ada@example.com", subject="Hi", text="Hello")
assert result.attempts == 2
async def test_bad_credentials_surface_as_one_error(mail, outbox):
outbox.fail_next(AuthenticationError("bad key"))
with pytest.raises(AuthenticationError):
await mail.send(to="ada@example.com", subject="Hi", text="Hello")Testing templates
Render templates directly to check copy and data without any provider:
def test_receipt_shows_total(mail):
rendered = mail.render("receipt", {"order_id": 7, "total": "$12.00"})
assert rendered.subject == "Receipt for order 7"
assert "$12.00" in rendered.htmlDjango tests
Point the backend at a client built on a memory provider in your test settings, then assert on
the provider's outbox. Django's own locmem backend also still works if you prefer
django.core.mail.outbox.