Backend testing
pytest is the runner.
Layout
tests/
unit/ # pure domain and application logic, no I/O
integration/ # repositories, DB, DI wiring
api/ # HTTP behavior through TestClient
architecture/ # import-boundary checks; reads source files, so kept out of unit
conftest.py # shared fixtures
Feature-specific builders may live in a conftest.py next to the tests that use them. Mirror the source feature structure inside each folder when a suite grows.
Markers
Declare markers in pyproject.toml and apply them consistently so CI can filter:
unit, integration, api, architecture, slow
make test runs the fast suites (unit, architecture). make ci runs everything with the coverage gate.
Rules
- Unit tests touch no database, network, or filesystem.
- API tests use
TestClientandapp.dependency_overridesto swap ports for fakes or a test database. - Integration tests that depend on PostgreSQL behavior run against real PostgreSQL (container locally, service in CI). SQLite in-memory is fine only when dialect does not matter.
- Reset state per test: transaction rollback or table cleanup. Tests must not depend on order.
- Test auth, validation errors, and not-found paths for every endpoint, not only the happy path.
Architecture tests
Add tests/architecture/test_architecture_rules.py, which parses imports with ast and fails when a layer imports one it must not. The rules come from the table in FastAPI structure.
Commands
make test # fast suites
pytest -m integration # integration only
pytest --cov=src/app --cov-fail-under=80
See the coverage gate.