FastAPI:如何通过覆盖 `Depends()` 中的函数来测试 API

FastAPI: How to test APIs by overriding functions in `Depends()`

我对 FastAPI 测试非常非常陌生,所以任何正确方向的指导都将不胜感激。

所以我现在的情况如下:

一个非常简单的路由文件: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
    }

我希望能够对此进行测试。我查看了 FastAPI Testing Dependencies guide here。但这根本没有帮助,因为它对我不起作用。


对于我的测试,我现在拥有的是这样的:

文件: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"
    }

我希望在测试中,response = client.get() 将基于覆盖函数 override_dep,它将取代 some_function_is.

我认为 response.json() 会是:

{
    'datapoint_name': 'abcdef',
    'state': 'Test'
}

相反,它是:

{
    'datapoint_name': 'abcdef',
    'state': 'Actual'
}

这意味着测试中的override_dep功能没有用

我什至检查了 app.dependency_overrides 的值,它显示了正确的地图:

(Pdb) app.dependency_overrides
{<function some_function_is at 0x102b3d1b0>: <function override_dep at 0x102b3e0e0>}

函数的内存值匹配的地方:

(Pdb) some_function_is
<function some_function_is at 0x102b3d1b0>

(Pdb) override_dep
<function override_dep at 0x102b3e0e0>

我做错了什么?

您正在测试中创建 FastAPI app 对象 ,但您使用的是已定义的 路由器 你的TestClient。由于此路由器从未在应用程序中注册,因此覆盖应用程序的依赖项不会做任何有用的事情。

TestClient 通常与根应用程序一起使用(以便针对应用程序本身进行测试 运行):

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)

然后是测试:

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"
    }

这按预期通过了,因为您现在正在针对应用程序(TestClient(app))进行测试 - 您覆盖依赖项的位置。

确实解决了问题,我想提出另一个改进建议。

覆盖测试文件根目录中的 depends 函数,由于缺乏清理,引入了干扰以下测试的风险。

相反,我建议使用夹具,这将确保测试的隔离。我写了一个简单的pytest插件来集成FastAPI的依赖系统来简化语法。

通过以下方式安装它:pip install pytest-fastapi-deps 然后像这样使用它:

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"
        }