Unit 07.03: Overriding a dependency in a test
Overriding a dependency is what lets the whole request path run without the expensive part.
The same code path, a different implementation
An endpoint depending on a model, with the dependency replaced.
The code calls it before and after the override.
from fastapi import Depends, FastAPI
from fastapi.testclient import TestClient
def get_model():
return {"name": "real-model", "cost": "money"}
app = FastAPI()
@app.get("/predict")
def predict(model=Depends(get_model)) -> dict:
return {"used": model["name"]}
client = TestClient(app)
print("without override:", client.get("/predict").json())
app.dependency_overrides[get_model] = lambda: {"name": "fake-model", "cost": "none"}
print("with override :", client.get("/predict").json())
app.dependency_overrides.clear()
print("""
`dependency_overrides` is the reason to inject the model rather than import
it. The whole request path -- routing, validation, response model, error
handling -- runs, with no network call and no cost.
""")
Routing, validation, the response model and error handling all ran. The only thing replaced was the part that costs money and needs a network.
This is the payoff for the injection in the previous units. An imported module-level client cannot be replaced this way, which is why the structural decision comes first and the testing benefit follows.
The mistake this prevents
The mistake is patching the module's attributes in tests to achieve the same thing. It works and it depends on import order and internal names, so it breaks on refactors that changed nothing about behaviour.
Takeaway
dependency_overrides replaces the expensive dependency while the whole request path still runs. It is the reason to inject rather than import.
