Reputation: 474
I am very, very new to FastAPI testing, so any guidance in the right direction would be appreciated.
So what I have right now is as follows:
A very simple routes file: datapoint_routes.py
from fastapi import APIRouter, Depends
datapoint_router = APIRouter()
def some_function_is():
return "Actual"
@datapoint_router.get('/{datapoint_name}')
def get_db(
datapoint_name: str,
some_function_output=Depends(some_function_is)
) -> dict:
return {
'datapoint_name': datapoint_name,
'state': some_function_output
}
I want to be able to test this. I checked out FastAPI Testing Dependencies guide here. But this did not help at all, because it didn't work for me.
For my tests, what I have right now is something like this:
File: test_datapoint_router.py
from typing import Union
from fastapi import FastAPI
from fastapi.testclient import TestClient
from datapoint_routes import datapoint_router, some_function_is
DATAPOINT_NAME = 'abcdef'
app = FastAPI()
client = TestClient(datapoint_router)
def override_dep(q: Union[str, None] = None):
return "Test"
app.dependency_overrides[some_function_is] = override_dep
def test_read_main():
response = client.get(f"/{DATAPOINT_NAME}")
assert response.status_code == 200
assert response.json() == {
'datapoint_name': DATAPOINT_NAME,
'state': "Test"
}
I would hope in the test, the response = client.get()
would be based on the overriding function override_dep
, which would replace some_function_is
.
I thought the response.json()
would be:
{
'datapoint_name': 'abcdef',
'state': 'Test'
}
instead, it is:
{
'datapoint_name': 'abcdef',
'state': 'Actual'
}
This means that the override_dep
function in the test is useless.
I even checked out the value of app.dependency_overrides
, and it shows a correct map:
(Pdb) app.dependency_overrides
{<function some_function_is at 0x102b3d1b0>: <function override_dep at 0x102b3e0e0>}
Where the memory values of functions do match:
(Pdb) some_function_is
<function some_function_is at 0x102b3d1b0>
(Pdb) override_dep
<function override_dep at 0x102b3e0e0>
What am I doing wrong?
Upvotes: 0
Views: 1987
Reputation: 2474
MatsLindh's answer does solve the problem, and I would like to suggest another improvement.
Overriding the depends function at the root of the test file, introduces a risk of interfering with the following tests, due to a lack of cleanup.
Instead, I suggest using a fixture, which would ensure the isolation of your tests. I wrote a simple pytest plugin to integrate with the dependency system of FastAPI to simplify the syntax as well.
Install it via: pip install pytest-fastapi-deps
and then use it like so:
from typing import Union
from fastapi.testclient import TestClient
from datapoint_routes import app, datapoint_router, some_function_is
DATAPOINT_NAME = 'abcdef'
client = TestClient(app)
def override_dep(q: Union[str, None] = None):
return "Test"
def test_read_main_context_manager(fastapi_dep):
with fastapi_dep(app).override({some_function_is: override_dep}):
response = client.get(f"/{DATAPOINT_NAME}")
assert response.status_code == 200
assert response.json() == {
'datapoint_name': DATAPOINT_NAME,
'state': "Test"
}
Upvotes: 1
Reputation: 52802
You're creating the FastAPI app
object in your test, but you're using a defined router with your TestClient
. Since this router is never registered with the app, overriding a dependency with the app won't do anything useful.
The TestClient is usually used with the root app (so that the tests run against the app itself):
from fastapi import APIRouter, Depends, FastAPI
app = FastAPI()
datapoint_router = APIRouter()
def some_function_is():
return "Actual"
@datapoint_router.get('/{datapoint_name}')
def get_db(
datapoint_name: str,
some_function_output=Depends(some_function_is)
) -> dict:
return {
'datapoint_name': datapoint_name,
'state': some_function_output
}
app.include_router(datapoint_router)
And then the test:
from typing import Union
from fastapi.testclient import TestClient
from datapoint_routes import app, datapoint_router, some_function_is
DATAPOINT_NAME = 'abcdef'
client = TestClient(app)
def override_dep(q: Union[str, None] = None):
return "Test"
app.dependency_overrides[some_function_is] = override_dep
def test_read_main():
response = client.get(f"/{DATAPOINT_NAME}")
assert response.status_code == 200
assert response.json() == {
'datapoint_name': DATAPOINT_NAME,
'state': "Test"
}
This passes as expected, since you're now testing against the app (TestClient(app)
) - the location where you overrode the dependency.
Upvotes: 2