tezvyn:

Testing FastAPI Lifespan Events

AI-drafted, machine-checkedSource: fastapi.tiangolo.comintermediate

FastAPI lifespan events only run when TestClient is used as a context manager. Use this to test startup logic like DB pools before endpoints. Using TestClient(app) without with skips lifespan, leaving your app uninitialized and tests silently wrong.

WHY IT EXISTS: FastAPI applications often need to perform work before the first request arrives and cleanup after the last one leaves. Database engines must be created, connection pools opened, caches populated, and message queue consumers started. Without a reliable way to test this lifecycle, integration tests would exercise endpoints against an uninitialized application that does not match production behavior. The lifespan protocol exists to wrap the entire application lifetime in a single async context manager, and testing it requires the test harness to respect that same boundary.

THE MENTAL MODEL: Think of lifespan as the mains power strip for your application. When you flip the switch, everything boots in order; when you flip it off, teardown runs in reverse. TestClient without a with statement is like probing a circuit with the power off: the wires are there, but nothing is running. Using with TestClient(app) as client is flipping the switch, giving you a live system where startup has finished and shutdown is guaranteed to run when the block ends.

HOW IT WORKS: You define an async context manager decorated with asynccontextmanager. The code before yield is startup, and the code after yield is shutdown. You pass this lifespan function to your FastAPI app. In tests, you instantiate TestClient inside a with statement. Entering the block triggers the startup phase, so any state populated there is visible to your test. Exiting the block triggers the shutdown phase, so resources are released and global state is reset. If you call client.get outside the with block after the client has closed, the lifespan has already torn down.

WHEN TO USE IT: Use this pattern whenever your application relies on state or resources created during startup. Examples include SQLAlchemy engine initialization, Redis connection pool warming, loading machine learning models into memory, starting background task processors, or populating in-memory lookup tables. It is also essential when testing endpoints that read from state set by lifespan, because those endpoints will fail or return wrong data if the state is missing.

WHEN NOT TO USE IT: Do not use the context manager form when you are testing code that deliberately avoids lifespan, such as utility functions or standalone Pydantic models. If your test only checks path operation logic and never touches startup-dependent state, the overhead is unnecessary. Also avoid nesting multiple TestClient context managers in the same test without careful state management, because overlapping lifespans can create conflicting global state or connection pool exhaustion.

ONE CANONICAL EXAMPLE: You have a lifespan that inserts two items into a shared dictionary before yield and clears the dictionary after yield. Outside the with TestClient block, the dictionary is empty. Inside the block, you assert the dictionary contains the seeded items, then call client.get to read them through the endpoint. When the block ends, the dictionary is empty again. This proves startup ran, the endpoint saw the data, and shutdown cleaned up.

Read the original → fastapi.tiangolo.com

Get five bites like this every day.

Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.