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.

Want structured learning?

Take the full Fastapi course →