'Best way to override FastAPI dependencies for testing with a different dependency for each test
According to FastAPI official documentation the recommended way to override the dependencies for testing is to do it globally before all tests are run:
async def override_dependency(q: Optional[str] = None):
return {"q": q, "skip": 5, "limit": 10}
app.dependency_overrides[common_parameters] = override_dependency
def test_override_in_items():
response = client.get("/items/")
assert response.status_code == 200
assert response.json() == {
"message": "Hello Items!",
"params": {"q": None, "skip": 5, "limit": 10},
}
def test_override_in_items_with_q():
response = client.get("/items/?q=foo")
assert response.status_code == 200
assert response.json() == {
"message": "Hello Items!",
"params": {"q": "foo", "skip": 5, "limit": 10},
}
But this lets you override the dependency just once for the entire test run. What if I need to have different dependencies for each test? Is it safe to override the dependency inside of the test body and reset them after the test has finished? Like this
def test_override_in_items():
app.dependency_overrides[common_parameters] = override_dependency
response = client.get("/items/")
assert response.status_code == 200
assert response.json() == {
"message": "Hello Items!",
"params": {"q": None, "skip": 5, "limit": 10},
}
app.dependency_overrides[common_parameters] = {}
What are the drawbacks of doing it like the example above?
Solution 1:[1]
This is what I use myself to override dependencies in my tests. You have to use it as a context manager with a with
statement (See examples below). Upon entering the context it saves existing overrides it encounters. These will be restored when exiting the context.
import typing
from fastapi import FastAPI
class DependencyOverrider:
def __init__(
self, app: FastAPI, overrides: typing.Mapping[typing.Callable, typing.Callable]
) -> None:
self.overrides = overrides
self._app = app
self._old_overrides = {}
def __enter__(self):
for dep, new_dep in self.overrides.items():
if dep in self._app.dependency_overrides:
# Save existing overrides
self._old_overrides[dep] = self._app.dependency_overrides[dep]
self._app.dependency_overrides[dep] = new_dep
return self
def __exit__(self, *args: typing.Any) -> None:
for dep in self.overrides.keys():
if dep in self._old_overrides:
# Restore previous overrides
self._app.dependency_overrides[dep] = self._old_overrides.pop(dep)
else:
# Just delete the entry
del self._app.dependency_overrides[dep]
You can use it like this in your tests:
from app.main import app
from fastapi.testclient import TestClient
def test_override_in_items(client: TestClient):
with DependencyOverrider(app, overrides={common_parameters: override_dependency}):
response = client.get("/items/")
assert response.status_code == 200
assert response.json() == {
"message": "Hello Items!",
"params": {"q": None, "skip": 5, "limit": 10},
}
Or one could use it as a fixture in pytest:
@pytest.fixture(scope="function")
def faked_common_parameters():
with DependencyOverrider(
app, overrides={common_parameters: override_dependency}
) as overrider:
yield overrider
def test_override_in_items(client: TestClient, faked_common_parameters):
# Running with the overridden dependency
response = client.get("/items/")
assert response.status_code == 200
assert response.json() == {
"message": "Hello Items!",
"params": {"q": None, "skip": 5, "limit": 10},
}
Solution 2:[2]
You can reset your overrides by setting app.dependency_overrides to be an empty dict:
app.dependency_overrides = {}
Solution 3:[3]
According to official documentation: https://fastapi.tiangolo.com/advanced/testing-dependencies/
If you want to override a dependency only during some tests, you can set the override at the beginning of the test (inside the test function) and reset it at the end (at the end of the test function).
Based on this, we can conclude that the author of the framework does not see any danger in this.
But user Vlad is right. You may have a dependency common to several tests and a dependency used only in one test. Having dropped all dependencies in one test, you will not pass the subsequent ones that use common dependencies. This can be attributed to inconveniences.
Therefore, in my opinion, for convenience, it is better to use some kind of auxiliary mechanism proposed here
Solution 4:[4]
I used Mat's answer and created an open-source library that adds a fixture based on the code snippet. See it here.
To use the library simply do: pip install pytest-fastapi-deps
, then you'll have the fastapi_dep
fixture. Use it like so:
import pytest
from fastapi.testclient import TestClient
client = TestClient(app)
def test_get_override_single_dep(fastapi_dep):
with fastapi_dep(app).override({common_parameters: lambda: {"my": "override"}}):
response = client.get("/items/")
assert response.status_code == 200
assert response.json() == {"my": "override"}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | Dan |
Solution 3 | Maksim |
Solution 4 | Peter K |