FastAPI dependencies are surprisingly easy to override in tests, letting you swap out live services for predictable mocks without changing your application code.
Let’s see how this works with a simple example. Imagine you have a FastAPI app that fetches user data from an external API.
from fastapi import FastAPI, Depends, HTTPException
import httpx
app = FastAPI()
async def get_external_user_data(user_id: int):
# In a real app, this would be a proper external API call
# For this example, we'll simulate it
try:
response = httpx.get(f"https://api.example.com/users/{user_id}")
response.raise_for_status() # Raise an exception for bad status codes
return response.json()
except httpx.RequestError as exc:
raise HTTPException(status_code=503, detail=f"External service unavailable: {exc}")
except httpx.HTTPStatusError as exc:
raise HTTPException(status_code=exc.response.status_code, detail=f"External service error: {exc.response.text}")
@app.get("/users/{user_id}")
async def read_user(user_data: dict = Depends(get_external_user_data)):
return {"user_info": user_data}
Now, if we wanted to test the /users/{user_id} endpoint, we’d normally hit the actual api.example.com. That’s slow, unreliable, and potentially expensive. We want to mock get_external_user_data.
The key is app.dependency_overrides. You can add a dictionary to your FastAPI application instance that maps dependency callables to new callables. When FastAPI encounters a Depends on a callable that’s in dependency_overrides, it uses the overridden version instead.
Here’s how you’d test the above endpoint using httpx’s MockTransport:
from fastapi.testclient import TestClient
from unittest.mock import AsyncMock
import pytest
# Assuming the above FastAPI app is in a file named main.py
from main import app, get_external_user_data
client = TestClient(app)
@pytest.mark.asyncio
async def test_read_user_success():
# Create a mock for the dependency function
mock_user_data = {"id": 1, "name": "Test User"}
mock_dependency = AsyncMock(return_value=mock_user_data)
# Override the original dependency with the mock
app.dependency_overrides[get_external_user_data] = mock_dependency
response = client.get("/users/1")
assert response.status_code == 200
assert response.json() == {"user_info": mock_user_data}
# Assert that the mock was called with the correct arguments
mock_dependency.assert_awaited_once_with(user_id=1)
# IMPORTANT: Clean up the override after the test
del app.dependency_overrides[get_external_user_data]
@pytest.mark.asyncio
async def test_read_user_external_unavailable():
# Mock to simulate an external service error
mock_dependency = AsyncMock(side_effect=httpx.RequestError("Connection timed out"))
app.dependency_overrides[get_external_user_data] = mock_dependency
response = client.get("/users/1")
assert response.status_code == 503
assert "External service unavailable" in response.json()["detail"]
del app.dependency_overrides[get_external_user_data]
The core idea is that app.dependency_overrides acts as a lookup table. When FastAPI resolves Depends(get_external_user_data), it first checks app.dependency_overrides. If get_external_user_data is a key, it uses the corresponding value (your mock callable) instead of executing the original function. This interceptor pattern allows you to inject any behavior you want.
The del app.dependency_overrides[get_external_user_data] step is crucial. If you don’t remove overrides, they persist across tests, potentially causing unexpected behavior in subsequent tests that rely on the original dependency. This is why you often see dependency overrides set up within test functions or fixtures that clean up after themselves.
A common pitfall is forgetting to clean up the overrides. If you have multiple tests that use the same dependency but expect different mocked behavior, leaving an override in place from a previous test will cause the current test to fail. Always ensure your test environment is clean.
The next step is understanding how to override dependencies that are themselves injected via other dependencies.