← Python Code FastAPI
Browse Python Concepts

Testing FastAPI Endpoints — Override Persistence Leak

Mental Model

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.

Rule: Always clear app.dependency_overrides inside your test setup and teardown fixtures to isolate unit test states.

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?

Broken code
Python
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"}
Predict whether test_production_access will pass or fail.

The Output

What actually happens
AssertionError: assert {'access': 'denied'} == {'access': 'granted'}

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

Corrected pattern
Python
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

Always clear app.dependency_overrides inside your test setup and teardown fixtures to isolate unit test states.
Common mistake: Modifying global or application-wide state, like app.dependency_overrides, within a test case without cleaning it up afterward, leading to test isolation issues and flaky tests.