Testing
Z8ter provides drop-in fakes and helpers (z8ter.testing) so application
tests never need a database or mail server.
Test client
Z8ter apps are Starlette apps under the hood — use Starlette's
TestClient (requires httpx, included in the dev extra):
from starlette.testclient import TestClient
client = TestClient(app.starlette_app)
response = client.get("/")
Use the client as a context manager when the app relies on lifespan startup (e.g., background tasks):
with TestClient(app.starlette_app) as client:
...
In-memory repositories
InMemorySessionRepo and InMemoryUserRepo implement the full
SessionRepo/UserRepo contracts (hashing, expiry, revocation, rotation)
backed by plain dicts:
import pytest
from z8ter.auth.crypto import hash_password
from z8ter.testing import InMemorySessionRepo, InMemoryUserRepo
@pytest.fixture()
def user_repo():
repo = InMemoryUserRepo()
repo.create_user(
email="ada@example.com",
password_hash=hash_password("correct horse"),
name="Ada",
)
return repo
InMemoryUserRepo mirrors the SQLiteUserRepo surface
(create_user, get_user_by_email, email_exists, update_password,
…), so application code runs unchanged against either.
Building a test app
create_test_app() assembles a minimal app with sane test defaults
(fixed secret key, error handlers, optional auth wiring):
from starlette.testclient import TestClient
from z8ter.testing import InMemorySessionRepo, InMemoryUserRepo, create_test_app
def test_login_flow(user_repo):
app = create_test_app(
session_repo=InMemorySessionRepo(),
user_repo=user_repo,
)
client = TestClient(app.starlette_app)
...
For full-stack tests against your real project (templates, pages, Vite), point the path resolver at the project directory instead:
import z8ter
z8ter.set_app_dir("path/to/project")
Capturing email
Use InMemoryEmailProvider and assert on its outbox:
from z8ter.testing import InMemoryEmailProvider
provider = InMemoryEmailProvider()
builder.use_email(provider=provider, default_from="noreply@test")
...
assert "Verify" in provider.outbox[0].subject