FastAPI - 模拟路径函数没有效果

uma*_*mat 11 python pytest python-unittest starlette fastapi

我有一个简单的FastAPI应用程序,我正在尝试为其创建测试pytest

\n\n

我的目标是测试应用程序在出现不同错误时的行为方式。

\n\n

我的应用程序中有一个简单的健康检查路线:

\n\n
from fastapi import APIRouter\n\nrouter = APIRouter()\n\n\n@router.get("/health")\nasync def health():\n    return "It\'s working \xe2\x9c\xa8"\n
Run Code Online (Sandbox Code Playgroud)\n\n

现在在我的 pytest 模块中,我正在尝试修补上述函数,以便它引发不同的错误。\n我正在使用,unittest.mock但我得到了非常奇怪的行为。

\n\n
import pytest\nfrom unittest import mock\n\nfrom fastapi import HTTPException\nfrom starlette.testclient import TestClient\n\nimport app.api.health\nfrom app.main import app  # this is my application (FastAPI instance) with the `router` attached\n\n\n@pytest.fixture()\ndef client():\n    with TestClient(app) as test_client:\n        yield test_client\n\n\ndef test_simple(client):\n    def mock_health_function():\n        raise HTTPException(status_code=400, detail=\'gibberish\')\n\n    with mock.patch(\'app.api.health.health\', mock_health_function):\n        response = client.get(HEALTHCHECK_PATH)\n\n        with pytest.raises(HTTPException):  # this check passes successfully - my exception is raised\n            app.api.health.health()\n\n    assert response.status_code != 200  # this check does not pass. The original function was called as if nothing was patched\n
Run Code Online (Sandbox Code Playgroud)\n\n

尽管测试内部调用了完全相同的函数,但当我到达端点时,API 测试客户端仍然调用原始函数。

\n\n

为什么mock.patch测试中不直接调用函数就不能正常工作?

\n\n

或者也许我应该以某种不同的方式解决我的问题?

\n

Tho*_*mas 11

您可以使用monkeypatch固定装置来修补您的功能。

\n

首先拉出你想要修补的代码部分:

\n
from fastapi import FastAPI\n\napp = FastAPI()\n\n\ndef response():\n    return "It\'s working \xe2\x9c\xa8"\n\n\n@app.get("/health")\nasync def health():\n    return response()\n
Run Code Online (Sandbox Code Playgroud)\n

然后在测试中使用monkeypatch

\n
import pytest\n\nfrom fastapi import HTTPException\nfrom starlette.testclient import TestClient\n\nfrom app import main\n\n\ndef mocked_response():\n    raise HTTPException(status_code=400, detail=\'gibberish\')\n\n\n@pytest.fixture()\ndef client():\n    from app.main import app\n\n    with TestClient(app) as test_client:\n        yield test_client\n\n\ndef test_simple(client, monkeypatch):\n\n    monkeypatch.setattr(main, "response", mocked_response)\n    resp = client.get("/health")\n    assert resp.status_code == 400\n    assert resp.json()["detail"] == "gibberish"\n
Run Code Online (Sandbox Code Playgroud)\n
\n

另一种方法是使用Dependenciesdependency_overrides。\n这可能不适用于所有场景,但对于您给定的用例来说它确实有效。

\n
from fastapi import FastAPI,  Depends\n\napp = FastAPI()\n\n\ndef response():\n    return "It\'s working \xe2\x9c\xa8"\n\n\n@app.get("/health")\nasync def health(resp=Depends(response)):\n    return resp\n
Run Code Online (Sandbox Code Playgroud)\n

在您的测试客户端中,您现在可以像这样覆盖依赖项:

\n
import pytest\n\nfrom fastapi import HTTPException\nfrom starlette.testclient import TestClient\n\nfrom app.main import response\n\n\ndef mocked_response():\n    raise HTTPException(status_code=400, detail=\'gibberish\')\n\n\n@pytest.fixture()\ndef client():\n    from app.main import app\n    app.dependency_overrides[response] = mocked_response\n\n    with TestClient(app) as test_client:\n        yield test_client\n\n\ndef test_simple(client):\n\n    resp = client.get("/health")\n\n    assert resp.status_code == 400\n    assert resp.json()["detail"] == "gibberish"\n
Run Code Online (Sandbox Code Playgroud)\n
\n

如果您需要向响应函数添加参数,您可以使用闭包模式

\n
def response_closure():\n    def response(arg):\n        return arg\n    return response\n\n\n@app.get("/health")\nasync def health(resp=Depends(response_closure)):\n    return resp("It\'s working \xe2\x9c\xa8")\n
Run Code Online (Sandbox Code Playgroud)\n