Dependency Injection & Testable Architecture
Decouple your services from concrete instantiation. Use FastAPI's Depends() to inject database sessions, repositories, and authentication contexts.
Hardcoded Instantiation vs FastAPI Dependency Injection
Hardcoding database connections inside functions prevents mock testing. Dependency injection turns dependencies into configurable parameters.
# ❌ Creates real DB connection inside handler
@router.post("/users")
def create_user(data: UserCreate):
db = SessionLocal() # Hardcoded dependency!
repo = SqlAlchemyUserRepository(db)
service = UserService(repo)
return service.create_user(data)
# ✅ Injected automatically via Depends() provider
@router.post("/users")
def create_user(
data: UserCreate,
service: UserService = Depends(get_user_service)
):
return service.create_user(data)
⚡ The FastAPI Provider Chain
💻 Production Blueprint: Provider Factories & Pytest Overrides
Observe how `app.dependency_overrides` lets you swap out real database providers for mock implementations during automated testing.
# ─── 1. DEPENDENCY PROVIDER FACTORY (api/dependencies.py) ─────────────────
def get_user_service(repo: UserRepository = Depends(get_user_repository)) -> UserService:
return UserService(repository=repo)
# ─── 2. FASTAPI ROUTE CONSUMPTION (api/routes.py) ──────────────────────────
@router.post("/users", status_code=201)
def create_user(data: UserCreate, service: UserService = Depends(get_user_service)):
return service.create_user(data)
# ─── 3. PYTEST OVERRIDE IN MOCK TEST (tests/test_users.py) ─────────────────
def get_mock_user_service() -> UserService:
mock_repo = InMemoryUserRepository()
return UserService(repository=mock_repo)
# Override real database service with mock during test execution
app.dependency_overrides[get_user_service] = get_mock_user_service
def test_create_user_fast_mock():
client = TestClient(app)
res = client.post("/users", json={"email": "test@test.com", "password": "pass"})
assert res.status_code == 201