FastAPI 测试中如何处理 datetime.now() 等外部依赖?

问题描述 投票:0回答:1

我有一些现有的 FastAPI 测试不再通过,因为一些内部服务器逻辑已经更改,现在依赖于

datetime.now()
返回的值。

这是一个外部依赖项。通常,为了处理外部依赖项,我们会编写外部依赖项的模拟实现,并找到一种方法将其注入到正在测试的代码中。

我不确定这是否是像日期时间依赖这样简单的事情的最佳方法。可能是,也可能不是。

这是一些说明问题的 MWE 代码:

# lib_datetime.py

from datetime import datetime
from datetime import timezone

def now() -> datetime:
    return datetime.now(timezone.utc)
# fastapi_webserver.py

from fastapi import FastAPI

from lib_datetime import now

from datetime import datetime

app = FastAPI()

@app.get('/datetime_now')
def datetime_now():
    the_datetime_now = now()
    the_datetime_now_str = the_datetime_now.strftime('%Y-%m-%d %H:%M:%S.%f %z')
    return {
        'datetime_now': the_datetime_now_str,
    }
# test_fastapi_webserver.py

from fastapi.testclient import TestClient

from fastapi_webserver import app

from datetime import datetime
from datetime import timezone

client = TestClient(app)

def test_fastapi_webserver_datetime_now():

    datetime_now = datetime(year=2024, month=7, day=20, tzinfo=timezone.utc)
    datetime_now_str = datetime_now.strftime('%Y-%m-%d %H:%M:%S.%f %z')

    response = client.get('/datetime_now')
    print(response.json())
    assert response.status_code == 200
    assert response.json() == {
        'datetime_now', datetime_now_str,
    }

问题应该是相当明显的。返回的日期时间值根据测试运行的时间而变化。不好。

以下是我尝试解决此问题的摘要:

  • 猴子补丁。我尝试更改测试文件中
    lib_datetime.now()
    所指向的内容。我不知道如何进行这项工作。
  • 提升外部依赖。理论上,
    now()
    可能取决于一个对象。或者它可以成为一个物体。我们可以创建两个对象,一个返回当前日期时间,另一个返回用于测试的固定值。我不知道如何将其注入 FastAPI
    app
    ,或者即使这是可能的。这将是我最喜欢的方法,因为这是我最熟悉的方法。
  • 使用环境变量更改
    now()
    的运行时行为。同样,我不确定如何将其集成到测试代码中,并且我不确定这确实是一个好方法。

这里的障碍是我对 FastAPI 不太熟悉,因为我最近才开始使用它。所以我还不知道我能用它做什么。

python python-3.x fastapi
1个回答
0
投票

这是我学到的一些东西的总结,以及一些示例代码,将来可能对其他人有用。

  • 请勿使用
    Depends
  • FastAPI 有一个与
    Depends
    概念相关的依赖注入框架。不要将其用于全局概念,例如调用
    datetime.now()
    。在这种情况下它不起作用
  • Depends
    概念提供了一种从 FastAPI 路径(端点)定义中解决依赖关系的方法
  • 这里的问题是依赖解析框架仅在顶层工作
  • 如果您为诸如
    Depends
    调用之类的全局事物添加
    datetime.now
    类型的依赖项,则这在其他代码区域中不起作用
  • 您想要从代码中其他地方正确解决的任何依赖项都必须作为参数传递,无论在哪里
  • 传递像日志或
    now()
    函数这样的全局事物不是一个好的设计

而是滚动您自己的依赖项解析代码:

lib_datetime/
    __init__.py
    fake_datetime.py
    real_datetime.py
# lib_datetime/__init__.py

from lib_datetime.real_datetime import now as now_real
from lib_datetime.fake_datetime import now as now_fake


datetime_implementation_is_fake = False

def set_use_fake_now_implementation(use_fake_implementation: bool) -> None:
    global datetime_implementation_is_fake
    datetime_implementation_is_fake = use_fake_implementation

def now() -> datetime:
    if datetime_implementation_is_fake:
        return now_fake()
    else:
        return now_real()
# lib_datetime/fake_datetime.py

from datetime import datetime
from datetime import timezone


current_datetime = None

def now() -> datetime:
    if current_datetime is None:
        raise RuntimeError(f'datetime not set')
    return current_datetime

def set_current_datetime(current_datetime_value: datetime) -> None:
    global current_datetime
    current_datetime = current_datetime_value
# lib_datetime/real_datetime.py

from datetime import datetime
from datetime import timezone


def now() -> datetime:
    return datetime.now(timezone.utc)

使用方法:

# normal code:

from lib_datetime import now

current_dateime = now()

print(current_datetime)

# tests

from lib_datetime import set_use_fake_now_implementation
from lib_datetime.fake_datetime import set_current_datetime

current_datetime = datetime(
    year=2024, month=1, day=1,
    hour=9, minute=30, second=0,
    tzinfo=timezone.utc,
)
set_current_datetime(current_datetime_value=current_datetime)
set_use_fake_now_implementation(True)
© www.soinside.com 2019 - 2024. All rights reserved.