You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何解决FastAPI生命周期事件自动化测试失败问题

问题:FastAPI Lifespan 函数调用的自动化测试失败

我有一个FastAPI项目,定义了如下生命周期事件:

@asynccontextmanager
async def lifespan(app: FastAPI):
    # Starting up IAM APP
    if mm_settings.MIGRATE_CONFIGURATION:
        await migrate_configuration_service_providers()
    yield


app = FastAPI(root_path=mm_settings.ROOT_PATH, lifespan=lifespan)

我希望编写自动化测试,确保migrate_configuration_service_providers()及其内部函数被调用,但所有测试均失败。

尝试使用httpx.AsyncClient和FastAPI TestClient编写测试基类:

class LifespanTestBase(AsyncTestBase):
    @pytest.fixture()
    def test_client(self):
        with TestClient(app=app, base_url="http://test") as client:
            yield client

    @pytest_asyncio.fixture()
    async def async_client(self):
        async with AsyncClient(app=app, base_url="http://test") as client:
            yield client

对应的测试代码如下:

class TestLifespan(LifespanTestBase):
    @patch("iam.util.migrations.migrate_configuration_service_providers")
    # @pytest.mark.asyncio
    def test_should_call_migrate_configuration_service_providers(
        self,
        migrate_configuration_service_providers_mock: Mock,
        test_client: TestClient,
    ):
        test_client.get("/")
        migrate_configuration_service_providers_mock.assert_called()

    @patch("iam.util.migrations.migrate_configuration_service_providers")
    @pytest.mark.asyncio
    async def test_should_call_migrate_configuration_service_providers2(
        self,
        migrate_configuration_service_providers_mock: Mock,
        async_client: AsyncClient,
    ):
        await async_client.get("/")
        migrate_configuration_service_providers_mock.assert_called()

两类测试均失败,但手动运行应用时migrate_configuration_service_providers()可正常执行(能看到数据库变更)。怀疑测试失败可能是因为app未挂载路由,调用/接口返回404导致,请问能否编写有效的自动化测试,该如何解决此问题?


解决方案

1. 确认测试环境的配置开关

lifespan中仅当mm_settings.MIGRATE_CONFIGURATION为True时才会执行迁移函数,测试时必须确保这个配置处于开启状态:

def test_lifespan_migration():
    # 强制开启配置
    mm_settings.MIGRATE_CONFIGURATION = True
    # 后续测试逻辑

2. 直接测试lifespan逻辑,无需依赖接口

不需要调用任何接口,直接手动运行lifespan上下文即可触发启动阶段的代码,这样能排除路由问题的干扰:

@pytest.mark.asyncio
async def test_lifespan_triggers_migration():
    mm_settings.MIGRATE_CONFIGURATION = True
    # 替换为lifespan所在模块中引用的函数路径
    with patch("app.main.migrate_configuration_service_providers") as mock:
        async with lifespan(app):
            # 进入上下文时会执行yield前的迁移逻辑
            mock.assert_called_once()

3. 修正Mock的路径(核心问题)

Python的Mock是作用于模块的命名空间,必须确保你Mock的是被调用位置所引用的函数对象。比如:

  • 如果你的lifespan函数在app/main.py中,并且是通过from iam.util.migrations import migrate_configuration_service_providers导入的,那么Mock的路径应该是app.main.migrate_configuration_service_providers,而非iam.util.migrations.migrate_configuration_service_providers。

错误的Mock路径会导致Mock对象从未被调用,这是测试失败的常见原因。

4. 利用客户端上下文触发lifespan

如果坚持使用客户端测试,注意TestClient和AsyncClient在进入上下文时会自动执行lifespan的启动逻辑,不需要调用接口:

  • 同步客户端测试:
def test_sync_client_triggers_lifespan():
    mm_settings.MIGRATE_CONFIGURATION = True
    with patch("app.main.migrate_configuration_service_providers") as mock:
        with TestClient(app=app):
            # 进入上下文即触发lifespan启动
            mock.assert_called_once()
  • 异步客户端测试:
@pytest.mark.asyncio
async def test_async_client_triggers_lifespan():
    mm_settings.MIGRATE_CONFIGURATION = True
    with patch("app.main.migrate_configuration_service_providers") as mock:
        async with AsyncClient(app=app, base_url="http://test"):
            mock.assert_called_once()

内容的提问来源于stack exchange,提问作者Myrto Pirli

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 07:05:56