跳转到主要内容

简化FastAPI依赖项覆盖。

项目描述

fastapi-overrider

为您的 FastAPI 测试提供简单、安全的依赖项覆盖。

安装

pip install fastapi-overrider

动机

FastAPI提供了一种很好的覆盖依赖项的机制,但也有一些需要注意的地方

  • 覆盖不会自动清理,且无法轻松进行范围限定。
  • 当您只想使用一些测试数据时,需要编写大量样板代码。
  • 由于FastAPI在调用依赖项时依赖于对签名的检查,因此使用 unittest.mock.Mock 非常复杂。
  • 同样,模拟异步依赖项也很繁琐。

fastapi-overrider的目标是使依赖项覆盖变得简单、安全、可重用、可组合和可扩展。

用法

通用用法

将其用作pytest fixture以确保每个测试都使用一组干净的覆盖运行。

override = create_fixture(app)

def test_get_item_from_value(client: TestClient, override: Overrider) -> None:
    override_item = Item(item_id=0, name="Bar")
    override.value(lookup_item, override_item)

    response = client.get("/item/0").json()

    assert Item(**response) == override_item

或者用作上下文管理器

def test_get_item_context_manager(client: TestClient, app: FastAPI) -> None:
    with Overrider(app) as override:
        override_item = Item(item_id=0, name="Bar")
        override.value(lookup_item, override_item)

        response = client.get("/item/0").json()

        assert Item(**response) == override_item

在两种情况下,覆盖将在测试后清理。

上述示例还展示了如何仅用所需的返回值覆盖依赖项。Overrider将负责创建匹配的包装函数并将其设置为覆盖。

无论您的依赖项是否是异步的,Overrider都会正确处理。

基本覆盖

override.value() 返回覆盖值

def test_get_item_return_value(client: TestClient, override: Overrider) -> None:
    item = override.value(lookup_item, Item(item_id=0, name="Bar"))

    response = client.get("/item/0").json()

    assert Item(**response) == item

override.function() 接受一个可调用的对象

def test_get_item_function(client: TestClient, override: Overrider) -> None:
    item = Item(item_id=0, name="Bar")
    override.function(lookup_item, lambda item_id: item)  # noqa: ARG005

    response = client.get("/item/0").json()

    assert Item(**response) == item

将其用作app.dependency_overrides的替代品

def test_get_item_drop_in(client: TestClient, override: Overrider) -> None:
    item = Item(item_id=0, name="Bar")
    def override_lookup_item(item_id: int) -> Item:  # noqa: ARG001
        return item
    override[lookup_item] = override_lookup_item

    response = client.get("/item/0").json()

    assert Item(**response) == item

模拟和间谍

Overrider可以为您创建模拟

def test_get_item_mock(client: TestClient, override: Overrider) -> None:
    item = Item(item_id=0, name="Bar")
    mock_lookup = override.mock(lookup_item)
    mock_lookup.return_value = item

    response = client.get("/item/0")

    mock_lookup.assert_called_once_with(item_id=0)
    assert Item(**response.json()) == item

监视依赖项。原始依赖项仍然会被调用,但您可以调用断言并像unittest.mock.Mock一样检查它

def test_get_item_spy(client: TestClient, override: Overrider) -> None:
    spy = override.spy(lookup_item)

    client.get("/item/0")

    spy.assert_called_with(item_id=0)

自动生成的覆盖

Overrider可以使用Unifactory自动生成模拟对象。

要启用此额外功能,请使用pip install fastapi-overrider[unifactory]

Overrider将自动使用来自Polyfactory的库存的匹配工厂来处理给定依赖项。

生成单个覆盖值。您可以向任何自动生成方法提供可选关键字参数,以将属性固定到特定值,如本例中的name

def test_get_some_item(client: TestClient, override: Overrider) -> None:
    item = override.some(lookup_item, name="Foo")

    response = client.get(f"/item/{item.item_id}")

    assert item.name == "Foo"
    assert item == Item(**response.json())

您也可以让Overrider生成多个覆盖值

def test_get_five_items(client: TestClient, override: Overrider) -> None:
    items = override.batch(lookup_item, 5)

    for item in items:
        response = client.get(f"/item/{item.item_id}")
        assert item == Item(**response.json())

尝试涵盖模型可以采取的全部形式

def test_cover_get_items(client: TestClient, override: Overrider) -> None:
    items = override.cover(lookup_item)

    for item in items:
        response = client.get(f"/item/{item.item_id}")
        assert item == Item(**response.json())

快捷方式

您可以直接调用Overrider,它会猜测您要做什么

如果您传递一个可调用的对象,它将像override.function()一样行动

def test_get_item_call_callable(client: TestClient, override: Overrider) -> None:
    item = Item(item_id=0, name="Bar")
    override(lookup_item, lambda item_id: item)  # noqa: ARG005

    response = client.get("/item/0").json()

    assert Item(**response) == item

如果您传递一个不可调用的对象,它将像override.value()一样行动

def test_get_item_call_value(client: TestClient, override: Overrider) -> None:
    item = override(lookup_item, Item(item_id=0, name="Bar"))

    response = client.get("/item/0").json()

    assert Item(**response) == item

如果您不传递任何内容,它将创建一个模拟

def test_get_item_call_mock(client: TestClient, override: Overrider) -> None:
    item = Item(item_id=0, name="Bar")
    mock_lookup = override(lookup_item)
    mock_lookup.return_value = item

    response = client.get("/item/0")

    mock_lookup.assert_called_once_with(item_id=0)
    assert Item(**response.json()) == item

高级模式

重用常见的覆盖。它们是可组合的,您可以拥有多个

@pytest.fixture()
def as_dave(app: FastAPI) -> Iterator[Overrider]:
    with Overrider(app) as override:
        override(get_user, User(name="Dave", authenticated=True))
        yield override

@pytest.fixture()
def in_the_morning(app: FastAPI) -> Iterator[Overrider]:
    with Overrider(app) as override:
        override(get_time_of_day, "morning")
        yield override

def test_get_greeting(client: TestClient, as_dave: Overrider, in_the_morning: Overrider) -> None:
    response = client.get("/")

    assert response.text == '"Good morning, Dave."'

使用您自己的便利方法扩展它

class MyOverrider(Overrider):
    def user(self, *, name: str, authenticated: bool = False) -> None:
        self(get_user, User(name=name, authenticated=authenticated))

@pytest.fixture()
def override(app: FastAPI):
    with MyOverrider(app) as override:
        yield override

def test_open_pod_bay_doors(client: TestClient, my_override: MyOverrider) -> None:
    my_override.user(name="Dave", authenticated=False)

    response = client.get("/open/pod_bay_doors")

    assert response.text == "\"I'm afraid I can't let you do that, Dave.\""

项目详情


下载文件

下载适合您平台的文件。如果您不确定选择哪个,请了解有关安装包的更多信息。

源分布

fastapi_overrider-0.7.2.tar.gz (5.7 kB 查看哈希值)

上传时间

构建分布

fastapi_overrider-0.7.2-py3-none-any.whl (5.9 kB 查看哈希值)

上传时间 Python 3

由以下赞助商支持

AWS AWS 云计算和安全赞助商 Datadog Datadog 监控 Fastly Fastly CDN Google Google 下载分析 Microsoft Microsoft PSF 赞助商 Pingdom Pingdom 监控 Sentry Sentry 错误记录 StatusPage StatusPage 状态页面