[Python]一个简单的指南:如何模拟FastAPI中的单位测试的依赖项?
#网络开发人员 #python #测试 #fastapi

tl;博士

  • 使用dependency_overrides字典来覆盖设置的依赖关系。字段是Depends的参数,相应的值是可呼出的值,它创建了相同类型的依赖项对象(Annotated的第一个参数)。
  • 为您的方便起见,在嘲笑课程依赖时使用unittest.mock.AsyncMock
  • 模仿功能或类依赖性,模型可可中应该没有参数。否则您将获得RequestValidationError

介绍

使用FastAPI框架时进行单元测试时,您可能必须模拟设置的依赖项。除了tutorial pages之外,您还将找到解释如何覆盖dependencies的页面。

但是,官方文档不包括班级依赖的情况,并且您需要避免一个较小的陷阱。在这篇文章中,我想分享我如何解决这些问题的经验。

1.初始设置

为了清楚起见,我想设置我能想到的最简单的示例。假设我们有一个端点 - /âor for get请求:

# example.py
from fastapi import FastAPI

from .dependencies import ExampleFunctionDependency, ExampleClassDependency

app = FastAPI()


@app.get("/")
def example_router(
    *,
    example_function_dependency: ExampleFunctionDependency,
    example_class_dependency: ExampleClassDependency
):
    return "example dependency!"

在此路由器中,我们将使用以下代码的两个依赖项:

# dependencies.py
from typing import Annotated

from fastapi import Depends


def example_function() -> int:
    return 1


class ExampleClass:
    ...


ExampleFunctionDependency = Annotated[int, Depends(example_function)]
ExampleClassDependency = Annotated[ExampleClass, Depends()]

请注意,我们在此处使用Annotated,这已被Fastapi since its version 0.95.0

2.嘲笑依赖性

让我们潜入嘲笑上面准备的依赖项。我们将从函数依赖项开始,该函数在官方文档中的示例。

功能依赖性

现在根据official documentation,我们将为路由器编写一个模拟依赖的路由器。

请记住,当您指定覆盖依赖项的列表时,键是Depends函数中的实际功能或类(不是简单的字符串值!)值为 callable 生成模仿依赖项对象的对象

因此,当您像以下内容一样模拟函数依赖关系时,

def example_function(query: str = Query()) -> int:
  # some code that returns `int`

ExampleDependency = Annotated[int, Depends(example_function)]

那么dependency_overrides字典应该是这样的:

def mock_example_function(query: str = Query()):
  return 42

app.dependency_overrides.update({
  example_function: mock_example_function
})

因此,我们的UNITSEST代码应该如下:

# test_example.py
from unittest import mock

import pytest
from fastapi.testclient import TestClient

from .example import app
from .dependencies import example_function, ExampleClass


@pytest.fixture
def client() -> TestClient:
    def mock_example_function() -> int:
        return 42

    app.dependency_overrides.update(
        {example_function: mock_example_function}
    )

    return TestClient(app=app)


def test_dependencies(client: TestClient):
    response = client.get("/")
    assert response.is_success

班级依赖性

但是班级依赖性呢?这是一个陷阱:由于我们将类实例用作类依赖性对象,因此我们需要提供一个可召唤的对象,该可callable生成该类的实例或IT的模拟对象

因此,在我们的情况下,以下任何一种情况都可以。但是,如果我们的班级在路由器内有许多方法要调用,那么最好使用嘲笑器(例如unittest.mock.AsyncMock)来简单起见(嗯,实际上,这就是模拟的目的)。请注意,我们不提供实例本身

# a function that provides either mocking object or an instance of an object
def mock_example_class() -> ExampleClass:
        return mock.AsyncMock()

app.dependency_overrides.update({ 
  ExampleClass: mock_example_class
})

# directly passes our custom mocking class
class CustomMockingClass:
  

app.dependency_overrides.update({ 
  ExampleClass: CustomMockingClass
})

如果我们选择了第一个选项,那么我们的client固定装置将是:

@pytest.fixture
def client() -> TestClient:
    def mock_example_function() -> int:
        return 42

    def mock_example_class() -> mock.AsyncMock:
        return mock.AsyncMock()

    app.dependency_overrides.update(
        {example_function: mock_example_function, ExampleClass: mock_example_class}
    )

    return TestClient(app=app)

现在让我们运行pytest查看以下结果!

Image description

警告:您的模拟可可中没有任何参数

这是因为,如果您在模拟可叫中意外使用参数,则将其视为FastApi

中的查询参数

当我试图用lambda表达式简化模拟部分时,我发生了这个问题:

@pytest.fixture
def client() -> TestClient:
    app.dependency_overrides.update(
        {example_function: lambda x: 42, ExampleClass: lambda x: mock.AsyncMock()}
    )

    return TestClient(app=app)

然后,当您运行pytest时,您将获得以下错误:

    def test_dependencies(client: TestClient):
        response = client.get(url="/")
>       assert response.is_success
E       assert False
E        +  where False = <Response [422 Unprocessable Entity]>.is_success

由于我们获得了422状态,因此我们可以怀疑该错误可能来自RequestValidationError。由于它超出了这篇文章的范围,因此我们不会挖掘如何检查错误,但原因是因为我们的参数x我们意外地放入lambdas被确认为查询参数(要查看详细信息,请检查详细信息,检查source code)。

现在我们知道了问题的确切原因,我们可以用lambda表达式简化代码:

@pytest.fixture
def client() -> TestClient:
    app.dependency_overrides.update(
        {example_function: lambda : 42, ExampleClass: lambda : mock.AsyncMock()}
    )

    return TestClient(app=app)

,如果您再次运行pytest,则测试应通过。

结论

FastApi中的嘲笑依赖性并不是看起来那么简单。通过减少开发人员的工作负载,Fastapi封装了许多逻辑以回报,一旦您想实现自己的逻辑,就很容易迷路。希望这篇文章可以帮助您测试FastAPI应用程序。