A testing strategy for FastAPI services that covers real databases, dependency failures, concurrency and API contracts—not only happy paths.

Production principle

Test observable behaviour across real boundaries, especially the paths where dependencies fail and state may be left incomplete.

01

Coverage is not confidence

Line coverage reports which statements executed. It does not show whether assertions protect the behaviour users depend on, whether the database enforces the expected rules or whether error paths preserve state. A test can execute every line through mocks while proving very little about the deployed system.

Start from risks and contracts instead of functions. For each important endpoint, identify accepted inputs, durable effects, emitted side effects, error responses and invariants that must remain true. Those observations become the test surface.

  • Use unit tests for isolated domain rules and fast feedback.
  • Use integration tests where SQL, transactions, serialization or framework wiring matter.
  • Use a small number of end-to-end checks for deployment-critical journeys.
02

Use the database you operate

SQLite is convenient but cannot validate PostgreSQL-specific constraints, isolation behaviour, data types or query plans. If production correctness depends on PostgreSQL, integration tests should run against an ephemeral PostgreSQL instance with the same migrations used by deployment.

Tests should verify both the API result and the resulting database state. After a forced exception, assert that partial records do not remain. For uniqueness and concurrency, exercise two independent sessions so the test does not accidentally serialize the race it is supposed to reveal.

03

Make dependencies fail deliberately

Timeouts, invalid responses, rate limits and temporary unavailability are normal properties of external services. A mock that always returns a successful object removes the exact uncertainty the production design must handle.

Model failures at the boundary: a timeout before any response, a retryable 503, a permanent 422, a malformed payload and a response that arrives after the caller has given up. Assert the public error contract, local state, retry decision and emitted telemetry for each case.

  • Do not assert private call order unless it is itself part of the contract.
  • Keep time controllable so backoff and expiry tests remain deterministic.
  • Verify that logs and metrics do not expose secrets or personal data.
04

Build a failure-path matrix

For one critical workflow, list its boundaries down the rows and failure moments across the columns: before local write, during commit, after commit, during external delivery and during retry. This produces a finite inspection plan rather than an open-ended demand to test more.

A production-ready suite does not need every possible combination. It needs representative evidence for each recovery mechanism and every invariant whose failure would be expensive or irreversible.

Continue the inspection

Explore all engineering notes.

Use PRODUCTION-7 to connect this concern with the other dimensions of a trustworthy backend.

View all articles Get the checklist