FastAPI Dependency Injection: Replacing Globals With Depends
Module-level globals for database connections, email clients, and clocks make FastAPI handlers impossible to unit-test - every test hits real services. FastAPI's Depends() system turns those globals into swappable dependencies, so tests inject fakes without monkey-patching.
The problem with globals in FastAPI handlers
A common early-stage pattern is to initialise resources at module scope and have handlers reach for them directly:
import psycopg2
from datetime import datetime, timezone
DB_CONN = psycopg2.connect(DATABASE_URL)
EMAIL_CLIENT = FakeEmailClient()
@app.post("/api/orders/{order_id}/confirm")
def confirm_order(order_id: int):
now = datetime.now(timezone.utc) # global call, can't be pinned
cur = DB_CONN.cursor() # global, can't be swapped
...
EMAIL_CLIENT.send(...) # global, sends real mail in tests
Parallel tests stomp on shared state, and there is no seam to inject a fake
without reaching into module.__dict__.
Define dependency factories
Replace each global with a plain function that returns the resource. FastAPI calls it once per request:
from fastapi import Depends
_db_conn = psycopg2.connect(DATABASE_URL) # private - only the factory touches it
_email_client = FakeEmailClient()
def get_db_conn():
return _db_conn
def get_email_client() -> FakeEmailClient:
return _email_client
def get_clock() -> datetime:
return datetime.now(timezone.utc)
Wire handlers with Depends()
Each handler declares what it needs as typed parameters. FastAPI resolves them:
@app.post("/api/orders/{order_id}/confirm")
def confirm_order(
order_id: int,
conn=Depends(get_db_conn),
emailer: FakeEmailClient = Depends(get_email_client),
now: datetime = Depends(get_clock),
):
cur = conn.cursor()
cur.execute(
"UPDATE orders SET status='confirmed', confirmed_at=%s WHERE id=%s "
"RETURNING customer_email, product",
(now, order_id),
)
row = cur.fetchone()
conn.commit()
cur.close()
if not row:
raise HTTPException(status_code=404, detail="order not found")
emailer.send(to=row[0], subject=f"Order #{order_id} confirmed",
body=f"Your {row[1]} is confirmed at {now.isoformat()}")
return {"order_id": order_id, "confirmed_at": now.isoformat()}
Swap dependencies for testing with dependency_overrides
app.dependency_overrides is the canonical test seam - no monkey-patching needed:
@app.post("/_set_clock")
def set_clock(body: dict):
iso = body.get("iso")
if not iso:
app.dependency_overrides.pop(get_clock, None)
return {"cleared": True}
fixed = datetime.fromisoformat(iso)
app.dependency_overrides[get_clock] = lambda: fixed
return {"set": fixed.isoformat()}
In a test client, pin the clock and assert the response reflects the fixed time - with no real database or email involved when you override those too.
Key rules
- A FastAPI dependency is just a callable.
Depends(fn)calls it once per request and passes the return value to the handler. - Private module-level singletons (
_db_conn) inside the factory are fine; what matters is that handler bodies never reference them directly. - Use
yieldin a dependency when you need teardown (closing a cursor, releasing a connection) - FastAPI runs the cleanup after the response is sent. - Override multiple dependencies at once: each key in
app.dependency_overridesis independent, so DB, email, and clock can all be faked simultaneously in one test.
Want to try it hands-on? HeyDevJob gives you this exact setup in a live cloud workspace in your browser - edit it, run it, and see it work. Free, nothing to install.
Try it in a workspace →What you'll practice
- Defining get_db_conn(), get_email_client(), and get_clock() as FastAPI dependency factories
- Wiring handlers to dependencies with Depends() instead of global references
- Swapping dependencies in tests with app.dependency_overrides
FAQ
How does FastAPI dependency injection work?
You define a plain function that returns a resource (db connection, client, etc.), then declare it in a handler as a parameter annotated with Depends(your_function). FastAPI calls the function per request and passes the result to the handler automatically.
How do I replace a FastAPI dependency in tests?
Use app.dependency_overrides - a dict where the key is the original dependency function and the value is the replacement callable. For example: app.dependency_overrides[get_clock] = lambda: fixed_datetime. Clear it after the test.
When should I use yield in a FastAPI dependency?
Use yield when the dependency needs cleanup after the response is sent - for example, closing a database cursor or releasing a lock. Everything before the yield runs on the way in; everything after runs as teardown.
Keep learning
Learn it by doing. Open this in a live cloud workspace, make the change yourself, and keep a record of the work you can share.
Open the workspace →