FastAPI Mock Dependencies
Introduction
When testing FastAPI applications, you'll often need to isolate the components you're testing from their dependencies. This is where mocking dependencies comes into play. Mocking is a technique that replaces real dependencies with simulated objects that mimic the behavior of the real ones.
In this tutorial, you'll learn how to effectively mock dependencies in your FastAPI applications to create reliable and efficient tests.
Why Mock Dependencies?
Before diving into the how, let's understand why mocking dependencies is important:
- Isolation: Testing components in isolation ensures that failures are specific to the component being tested.
- Speed: External dependencies like databases or APIs can slow down tests.
- Reliability: Tests shouldn't fail because of external service downtime.
- Control: Mocking allows you to simulate various scenarios including error conditions.
Understanding FastAPI Dependency Injection
FastAPI uses a dependency injection system that allows you to declare dependencies for your path operation functions. A typical dependency might look like this:
from fastapi import Depends, FastAPI
app = FastAPI()
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.get("/users/{user_id}")
async def read_user(user_id: int, db = Depends(get_db)):
user = db.query(User).filter(User.id == user_id).first()
return user
Basic Dependency Overriding
FastAPI provides a built-in way to override dependencies using the app.dependency_overrides
dictionary. This is ideal for testing as it allows you to replace real dependencies with mocks.
How to Override Dependencies
Here's a basic example of how to override a dependency:
# Original dependency
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
# Mock dependency for testing
def get_test_db():
db = TestSessionLocal()
try:
yield db
finally:
db.close()
# In your test
app.dependency_overrides[get_db] = get_test_db
Using pytest Fixtures for Dependency Overrides
To make your tests cleaner, you can use pytest fixtures to manage dependency overrides:
import pytest
from fastapi.testclient import TestClient
from myapp.main import app, get_db
@pytest.fixture
def test_client():
# Setup: Override the dependency
app.dependency_overrides[get_db] = get_test_db
# Create a test client
client = TestClient(app)
# Provide the client to the test
yield client
# Cleanup: Remove the override after the test
app.dependency_overrides = {}
# Using the fixture in a test
def test_read_user(test_client):
response = test_client.get("/users/1")
assert response.status_code == 200
assert response.json()["name"] == "Test User"
Mocking with unittest.mock
For more complex dependencies, you might want to use Python's unittest.mock
module:
from unittest.mock import MagicMock, patch
# Create a mock database
mock_db = MagicMock()
mock_db.query.return_value.filter.return_value.first.return_value = {
"id": 1,
"name": "Test User",
"email": "[email protected]"
}
# Override the dependency
def get_mock_db():
yield mock_db
# In your test setup
app.dependency_overrides[get_db] = get_mock_db
Advanced Mocking Techniques
Mocking External API Calls
If your application depends on external APIs, you'll want to mock those calls in your tests. Here's an example using unittest.mock
:
from unittest.mock import patch
def get_external_data():
response = requests.get("https://api.example.com/data")
return response.json()
# In your test
@patch("myapp.dependencies.requests.get")
def test_external_data(mock_get):
# Configure the mock
mock_response = MagicMock()
mock_response.json.return_value = {"data": "mock data"}
mock_get.return_value = mock_response
# Run the test
result = get_external_data()
assert result == {"data": "mock data"}
Mocking Authentication
Authentication dependencies are common in FastAPI applications. Here's how to mock them:
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
# Original dependency
async def get_current_user(token: str = Depends(oauth2_scheme)):
user = decode_token(token)
if not user:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
return user
# Mock dependency
async def get_mock_user():
return {"id": 1, "username": "testuser"}
# In your test
app.dependency_overrides[get_current_user] = get_mock_user
Real-world Example: Testing a User Service
Let's put it all together with a real-world example of testing a user service with mocked dependencies:
# app/dependencies.py
from fastapi import Depends
from sqlalchemy.orm import Session
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
def get_user_service(db: Session = Depends(get_db)):
return UserService(db)
# app/routes.py
@app.get("/users/{user_id}")
async def read_user(user_id: int, user_service = Depends(get_user_service)):
return user_service.get_user(user_id)
# tests/test_users.py
import pytest
from fastapi.testclient import TestClient
from unittest.mock import MagicMock
from app.main import app
from app.dependencies import get_user_service
@pytest.fixture
def mock_user_service():
service = MagicMock()
service.get_user.return_value = {
"id": 1,
"name": "Test User",
"email": "[email protected]"
}
return service
@pytest.fixture
def client(mock_user_service):
app.dependency_overrides[get_user_service] = lambda: mock_user_service
with TestClient(app) as client:
yield client
app.dependency_overrides = {}
def test_read_user(client, mock_user_service):
response = client.get("/users/1")
assert response.status_code == 200
assert response.json() == {
"id": 1,
"name": "Test User",
"email": "[email protected]"
}
mock_user_service.get_user.assert_called_once_with(1)
Best Practices for Mocking Dependencies
- Mock at the right level: Mock at the boundary of your system, not internal implementation details.
- Don't over-mock: If it's simple to use the real dependency, prefer that over mocking.
- Verify interactions: Assert that your mock was called as expected.
- Clean up after tests: Always reset dependency overrides after your tests.
- Keep mocks simple: Only mock the methods and behaviors you need for your test.
Common Pitfalls
Forgetting to Reset Overrides
Always reset app.dependency_overrides
after your tests to avoid test contamination:
# Setup
app.dependency_overrides[get_db] = get_test_db
# Test
# ...
# Cleanup
app.dependency_overrides = {}
Mocking Too Much
Mocking everything can lead to tests that pass but don't reflect reality. Strike a balance between isolation and realism.
Not Verifying Mock Usage
It's important to verify that your mock was called as expected:
mock_db.query.assert_called_once_with(User)
mock_db.query().filter.assert_called_once_with(User.id == 1)
Summary
Mocking dependencies in FastAPI is a powerful technique that allows you to write fast, reliable, and isolated tests. By using app.dependency_overrides
and Python's mocking tools, you can simulate any dependency and test your application in various scenarios.
Remember these key points:
- Use
app.dependency_overrides
to replace real dependencies with test versions - Consider using pytest fixtures to manage dependency overrides
- For complex behaviors, use Python's
unittest.mock
- Always clean up after your tests
- Strike a balance between isolation and realism
Additional Resources
Exercises
- Create a FastAPI application with a route that depends on a database connection, then write tests using a mocked database.
- Implement a route that calls an external API, then write tests using mocked API responses.
- Create a route with authentication, then write tests that mock the authentication process.
- Experiment with different assertion techniques to verify that your mocks are used as expected.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)