1. pytest fixtures and unit boundaries
Start with the contract: make inputs, outputs and failure behavior explicit before adding infrastructure. This keeps the feature easy to reason about and gives tests a stable boundary.
2. TestClient and dependency overrides
Apply the pattern through a small vertical slice. Keep framework wiring at the edge and business decisions in focused functions or services that can be tested without starting the whole application.
def override_session():
yield test_session
app.dependency_overrides[get_session] = override_session
def test_create_task(client):
response = client.post("/tasks", json={"title": "Ship it"})
assert response.status_code == 2013. PostgreSQL integration tests with Testcontainers
Treat failure paths as part of the design. Add bounded resource usage, meaningful errors and a verification step so the behavior remains dependable under real production conditions.
4. Hands-on Exercise
Build the feature
Write success, validation, authentication and not-found tests for the task API, then run the persistence suite against a disposable PostgreSQL container.
Definition of done
- The happy path works through the real HTTP boundary.
- At least one failure path is handled and tested.
- Configuration and secrets stay outside source code.
- The README explains how to run and verify the result.
5. Knowledge Check
Why override the session dependency instead of mocking SQLAlchemy everywhere?
Show answer
The HTTP and persistence layers still run together while the test controls isolation and cleanup at one boundary.