【问题标题】:FastAPI - mocking path function has no effectFastAPI - 模拟路径功能没有效果
【发布时间】:2020-07-06 07:34:29
【问题描述】:

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

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

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

from fastapi import APIRouter

router = APIRouter()


@router.get("/health")
async def health():
    return "It's working ✨"

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

import pytest
from unittest import mock

from fastapi import HTTPException
from starlette.testclient import TestClient

import app.api.health
from app.main import app  # this is my application (FastAPI instance) with the `router` attached


@pytest.fixture()
def client():
    with TestClient(app) as test_client:
        yield test_client


def test_simple(client):
    def mock_health_function():
        raise HTTPException(status_code=400, detail='gibberish')

    with mock.patch('app.api.health.health', mock_health_function):
        response = client.get(HEALTHCHECK_PATH)

        with pytest.raises(HTTPException):  # this check passes successfully - my exception is raised
            app.api.health.health()

    assert response.status_code != 200  # this check does not pass. The original function was called as if nothing was patched

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

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

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

【问题讨论】:

  • 您必须修补由 sut 导入的模块 - 请参阅 where to patch 文档。您目前正在修补测试中导入的函数。
  • 你能说明你是如何在你的测试代码中导入模拟函数的吗?
  • 就像问题中一样:我导入整个模块import app.api.health,然后直接调用函数app.api.health.health()。 (对不起那个嵌套)。 health 函数不会在其他任何地方导入,它只是在 FastAPI 路由器中注册 - 然后由框架调用。
  • 嗯,在这种情况下,补丁实际上看起来是正确的......
  • 似乎fastAPI的装饰器@route.get(...)存储了对原始函数的引用。模拟修补函数,但它发生在路由已经注册并且修补对象具有不同的 id 引用之后。

标签: python pytest python-unittest fastapi starlette


【解决方案1】:

您可以使用monkeypatch 夹具来修补您的功能。

先拉出你要打补丁的代码段:

from fastapi import FastAPI

app = FastAPI()


def response():
    return "It's working ✨"


@app.get("/health")
async def health():
    return response()

然后在你的测试中使用monkeypatch

import pytest

from fastapi import HTTPException
from starlette.testclient import TestClient

from app import main


def mocked_response():
    raise HTTPException(status_code=400, detail='gibberish')


@pytest.fixture()
def client():
    from app.main import app

    with TestClient(app) as test_client:
        yield test_client


def test_simple(client, monkeypatch):

    monkeypatch.setattr(main, "response", mocked_response)
    resp = client.get("/health")
    assert resp.status_code == 400
    assert resp.json()["detail"] == "gibberish"

另一种方法是使用Dependenciesdependencies_overrides。 这可能不适用于所有场景,但适用于您给定的用例。

from fastapi import FastAPI,  Depends

app = FastAPI()


def response():
    return "It's working ✨"


@app.get("/health")
async def health(resp=Depends(response)):
    return resp

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

import pytest

from fastapi import HTTPException
from starlette.testclient import TestClient

from app.main import response


def mocked_response():
    raise HTTPException(status_code=400, detail='gibberish')


@pytest.fixture()
def client():
    from app.main import app
    app.dependency_overrides[response] = mocked_response

    with TestClient(app) as test_client:
        yield test_client


def test_simple(client):

    resp = client.get("/health")

    assert resp.status_code == 400
    assert resp.json()["detail"] == "gibberish"

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

def response_closure():
    def response(arg):
        return arg
    return response


@app.get("/health")
async def health(resp=Depends(response_closure)):
    return resp("It's working ✨")

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-07-25
    • 2019-07-22
    • 1970-01-01
    • 2021-09-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-03
    相关资源
    最近更新 更多