Testing FastAPI Endpoints — Override Persistence Leak
app.dependency_overrides is a global 'switchboard' for the entire FastAPI application. Once you flip a switch (set an override), it stays in that position until you explicitly flip it back or reset the entire board. Tests must operate in isolated environments.
The Setup
You want to run unit tests on a secure API endpoint using FastAPI's standard TestClient. You write a series of tests where you mock a secure dependency in one scenario and expect default authentication rules to run in the next.
What Does This Print?
from fastapi import FastAPI, Depends
from fastapi.testclient import TestClient
app = FastAPI()
def get_admin_key():
return "prod_unbreakable_secret"
@app.get("/admin")
def admin_portal(key: str = Depends(get_admin_key)):
if key != "prod_unbreakable_secret":
return {"access": "denied"}
return {"access": "granted"}
client = TestClient(app)
def test_bypass_admin():
# Override validation key for simulation
app.dependency_overrides[get_admin_key] = lambda: "mocked_key"
res = client.get("/admin")
assert res.json() == {"access": "denied"}
def test_production_access():
# Expect system to run unmodified logic
res = client.get("/admin")
assert res.json() == {"access": "granted"}
test_production_access will pass or fail.
The Output
The second test test_production_access fails with an assertion mismatch error:
The validation system continues using the mocked override set in the first test, exposing a critical security test leakage.
Why Python Does This
FastAPI stores dependency replacements within a global dictionary variable called app.dependency_overrides. Because the app instance is declared globally in the module namespace, any mutations performed on dependency_overrides during a test execution block will persist across the lifetime of the process. Pytest does not automatically reload Python modules or clear instance-level states between test executions.
The Fix
from fastapi import FastAPI, Depends
from fastapi.testclient import TestClient
import pytest
app = FastAPI()
def get_admin_key():
return "prod_unbreakable_secret"
@app.get("/admin")
def admin_portal(key: str = Depends(get_admin_key)):
if key != "prod_unbreakable_secret":
return {"access": "denied"}
return {"access": "granted"}
# Utilize a Pytest fixture to reset overrides automatically
@pytest.fixture(autouse=True)
def cleanup_overrides():
app.dependency_overrides.clear()
yield
app.dependency_overrides.clear()
client = TestClient(app)
def test_bypass_admin():
app.dependency_overrides[get_admin_key] = lambda: "mocked_key"
res = client.get("/admin")
assert res.json() == {"access": "denied"}
def test_production_access():
res = client.get("/admin")
assert res.json() == {"access": "granted"}
By using a try...finally block or a pytest fixture with proper yield and teardown, app.dependency_overrides can be set at the beginning of a test and then reset or cleared in the finally block or after the yield. This ensures that any changes to the overrides are localized to that specific test and do not affect subsequent tests.
How This Fails in Real Systems
A security audit was performed on a health tracker API. Because dependency overrides leaked between test modules, authentications were bypassed in early tests, rendering subsequent permission tests green. When deployed, it contained an authentication bug that exposed patient profiles.
Key Takeaway
app.dependency_overrides, within a test case without cleaning it up afterward, leading to test isolation issues and flaky tests.