Skip to content

Dependency Overrides Interview Questions & Answers

11 questions Updated 2026-06-20 Share:

FastAPI dependency override interview questions — app.dependency_overrides, isolating tests, mocking auth, DB session swapping and cleanup patterns.

11 of 11

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 ways to practice

The self-quiz is live. Get notified when mock interviews and new question packs drop.

or
Join our WhatsApp Channel