app.dependency_overrides is a dict on the FastAPI instance. Map an original
dependency function to a replacement callable — FastAPI calls the replacement
instead during test requests.
from app.main import app
from app.auth import get_current_user
from fastapi.testclient import TestClient
def mock_user():
return {"id": 1, "name": "Alice", "role": "admin"}
app.dependency_overrides[get_current_user] = mock_user
client = TestClient(app)
def test_protected_endpoint():
resp = client.get("/admin/stats")
assert resp.status_code == 200
Rule of thumb: use dependency_overrides to replace any injected function —
DB sessions, auth, settings, external API clients — without touching app code.
Use a pytest autouse fixture to clear overrides after every test:
import pytest
from app.main import app
@pytest.fixture(autouse=True)
def reset_dependency_overrides():
yield
app.dependency_overrides.clear()
Or clear per-override:
@pytest.fixture
def with_mock_user():
app.dependency_overrides[get_current_user] = lambda: {"id": 1}
yield
del app.dependency_overrides[get_current_user]
Rule of thumb: always clean up overrides in fixture teardown — a leaked override in one test silently causes wrong behaviour in subsequent tests.
No — the override only needs to return a compatible value. FastAPI replaces the entire callable, so the override's parameters are independently resolved by DI.
# Original dep — reads from DB
async def get_current_user(token: str = Depends(oauth2_scheme)):
payload = decode_jwt(token)
return await db.get_user(payload["sub"])
# Override — returns a hardcoded user, no token needed
def mock_admin_user():
return User(id=1, name="Admin", role="admin")
app.dependency_overrides[get_current_user] = mock_admin_user
The override receives its own deps from DI — if it has parameters, they'll be injected. If it has none, it just returns the hardcoded value.
Rule of thumb: keep override functions as simple as possible — bare lambdas or zero-argument functions returning fake objects are ideal for tests.
Override the get_settings dependency (the @lru_cache wrapper):
from app.config import get_settings, Settings
from app.main import app
def test_settings():
test_settings = Settings(
database_url="sqlite+aiosqlite:///:memory:",
secret_key="test-only-secret",
debug=True,
)
app.dependency_overrides[get_settings] = lambda: test_settings
with TestClient(app) as client:
resp = client.get("/config")
assert resp.json()["debug"] is True
app.dependency_overrides.clear()
Also clear the @lru_cache to prevent stale settings:
get_settings.cache_clear()
Rule of thumb: always pair dependency_overrides[get_settings] with
get_settings.cache_clear() to prevent the cached production settings from
being used despite the override.
# conftest.py
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, Session
from fastapi.testclient import TestClient
from app.main import app
from app.db.session import get_db
from app.db.base import Base
TEST_ENGINE = create_engine("sqlite:///:memory:", connect_args={"check_same_thread": False})
TestingSessionLocal = sessionmaker(bind=TEST_ENGINE)
@pytest.fixture(scope="module", autouse=True)
def create_tables():
Base.metadata.create_all(bind=TEST_ENGINE)
yield
Base.metadata.drop_all(bind=TEST_ENGINE)
@pytest.fixture
def db_session():
session = TestingSessionLocal()
try:
yield session
finally:
session.rollback()
session.close()
@pytest.fixture
def client(db_session):
def override_get_db():
yield db_session
app.dependency_overrides[get_db] = override_get_db
with TestClient(app) as c:
yield c
app.dependency_overrides.clear()
Rule of thumb: roll back the test session instead of committing — it gives each test an isolated, empty state without truncating tables.
Inject the HTTP client as a dependency, then override it in tests:
# In app code
import httpx
async def get_http_client() -> httpx.AsyncClient:
async with httpx.AsyncClient() as client:
yield client
@app.get("/weather")
async def weather(city: str, client: httpx.AsyncClient = Depends(get_http_client)):
resp = await client.get(f"https://api.weather.com/{city}")
return resp.json()
# In tests — replace with a respx mock
import respx
import httpx
@respx.mock
def test_weather(client):
respx.get("https://api.weather.com/London").mock(
return_value=httpx.Response(200, json={"temp": 15})
)
resp = client.get("/weather?city=London")
assert resp.json()["temp"] == 15
Or inject a fake client via override:
async def fake_http_client():
yield FakeAsyncClient()
app.dependency_overrides[get_http_client] = fake_http_client
Rule of thumb: inject HTTP clients as dependencies rather than instantiating
them inside handlers — it makes them mockable without patch().
Override just the sub-dependency, leaving higher-level deps intact:
# Dependency chain: get_db → get_current_user → require_admin
# We want to test require_admin with a specific user but real DB logic
def fixed_user():
return User(id=99, name="Test Admin", role="admin")
# Override only get_current_user — get_db still uses real DI
app.dependency_overrides[get_current_user] = fixed_user
# require_admin still calls its Depends(get_current_user) — but gets our mock
FastAPI resolves the override at the point where the original dep is declared —
anything that Depends() on it gets the override's return value.
Rule of thumb: override at the lowest practical level — overriding get_current_user
(not require_admin) tests require_admin's logic with controlled user data.
Class-based deps are instantiated by FastAPI on each call. Override with another callable (class, function, or lambda) that returns a compatible object:
class PaginationDep:
def __init__(self, page: int = 1, size: int = 20):
self.skip = (page - 1) * size
self.limit = size
# Override with a fixed pagination
class FixedPagination:
skip = 0
limit = 5
app.dependency_overrides[PaginationDep] = FixedPagination
def test_first_page(client):
resp = client.get("/items")
data = resp.json()
assert len(data) <= 5
Rule of thumb: match the override's public interface (attributes/methods your handler uses) rather than its constructor — duck typing is sufficient.
Use a pytest fixture with function scope (the default):
@pytest.fixture
def admin_override():
app.dependency_overrides[get_current_user] = lambda: {"id": 1, "role": "admin"}
yield
del app.dependency_overrides[get_current_user]
def test_admin_only(client, admin_override):
resp = client.delete("/users/99")
assert resp.status_code == 200
def test_normal_user(client):
# admin_override not active — real get_current_user used
resp = client.delete("/users/99")
assert resp.status_code == 403
Rule of thumb: use per-test fixtures with explicit teardown for scoped overrides — don't set overrides directly in test bodies without a cleanup mechanism.
Replace the dependency with a MagicMock or a spy function:
from unittest.mock import MagicMock, call
mock_audit = MagicMock()
def audit_override(request: Request, user = Depends(get_current_user)):
mock_audit(path=str(request.url), user_id=user["id"])
app.dependency_overrides[audit_dep] = audit_override
def test_audit_logged(client):
client.get("/items")
mock_audit.assert_called_once()
call_args = mock_audit.call_args
assert call_args.kwargs["user_id"] == 1
Rule of thumb: spy on dependencies rather than internal functions — testing through the DI graph tests the real integration path.
Yes — yield-based overrides work exactly like regular yield dependencies:
class FakeDB:
def __init__(self):
self.data = [{"id": 1, "name": "Alice"}]
def get_all(self):
return self.data
async def fake_get_db():
db = FakeDB()
db.data.append({"id": 2, "name": "Bob"}) # setup
yield db
db.data.clear() # teardown (after handler)
app.dependency_overrides[get_db] = fake_get_db
The teardown (after yield) still runs after the handler completes — useful for
resetting state in the override itself.
Rule of thumb: use yield overrides when the fake resource needs setup and cleanup; use simple lambdas/functions for stateless fakes.
More Testing interview questions
More ways to practice
The self-quiz is live. Get notified when mock interviews and new question packs drop.