Testing with an In-Memory Database
Understand why tests need database isolation and how in-memory SQLite provides it
Why the current tests are broken
Your tests from Course 2 use a global client = TestClient(app) at the top of test_main.py. That client connects to the real app, which now uses SQLite. Two problems follow:
Tests share state. When test_create_expense creates an expense and test_list_expenses runs next, it sees the expense from the previous test. Tests that depend on a clean state fail unpredictably.
Tests write to the real database. Every expense created during a test run ends up in expenses.db. Running your tests fills your development database with fake data.
The fix: an in-memory database per test
SQLite supports in-memory databases: instead of a file path, you pass an empty URL ("sqlite://"). The database exists only in memory and disappears when the connection closes. Each test gets a completely fresh database.
engine = create_engine("sqlite://", connect_args={"check_same_thread": False})
SQLModel.metadata.create_all(engine)Swapping the session with dependency_overrides
Your endpoints use SessionDep — FastAPI calls get_session() and injects the result. To swap in a test session, you override the dependency:
app.dependency_overrides[get_session] = get_session_overrideget_session_override is a function that yields a session connected to the in-memory engine instead of the real database. FastAPI uses the override for every request made through the test client.
pytest fixtures
A pytest fixture is a function that sets up resources for tests and cleans up afterward. When a test function has a parameter with the same name as a fixture, pytest calls the fixture and passes the result in automatically.
You will define a fixture called client that:
- Creates an in-memory engine and builds the tables
- Overrides
get_sessionwith a function that yields sessions from that engine - Yields a
TestClientconnected to the app - Clears the dependency overrides after the test finishes
Every test that declares client as a parameter gets its own isolated database.