如何在 FastAPI 单元测试中正确 await HTTP 客户端请求

如何在 FastAPI 单元测试中正确 await HTTP 客户端请求

fastapi 测试中 `client.get()` 是同步方法,不能直接 `await`;需改用 `async with client.get(…) as response:` 语法获取异步响应对象,避免 `typeerror: object response can’t be used in ‘await’ expression`。

在 FastAPI 的异步测试场景中(如使用 pytest-asyncio),一个常见误区是误将测试客户端的 .get() 方法当作协程函数调用。实际上,TestClient(来自 fastapi.testclient.TestClient)是完全同步的 HTTP 客户端,其所有方法(包括 .get(), .post() 等)均返回普通 Response 对象,不支持 await —— 这正是报错 TypeError: object Response can’t be used in ‘await’ expression 的根本原因。

✅ 正确做法是:改用 httpx.AsyncClient 配合 @pytest.mark.asyncio,它才是为异步测试设计的官方推荐方案。TestClient 仅适用于同步测试;若需真正异步发起请求(例如测试中间件、流式响应或依赖事件循环的行为),必须切换到 AsyncClient。

以下是修正后的完整示例:

UXbot

UXbot

AI产品设计工具

下载

import pytest
from httpx import AsyncClient
from your_app.main import app  # 替换为你的 FastAPI 实例路径

@pytest.mark.asyncio
async def test_get_report(report_service, headers):
    """Test Report GET Response with true async client"""
    report_id = "sample-report-id"

    # Mock 服务层(确保其异步方法可被 await)
    report_service.query_db.query_fetchone = mock.AsyncMock(
        return_value={
            "id": "sample-id",
            "reportId": "sample-report-id",
            "reportName": "sample-report-name",
            "report": [],
        }
    )

    # 使用 httpx.AsyncClient(非 TestClient!)
    async with AsyncClient(app=app, base_url="http://test") as ac:
        response = await ac.get(f"/v2/report/{report_id}", headers=headers)

    assert response.status_code == 200
    assert response.json()["reportId"] == "sample-report-id"

⚠️ 注意事项:

  • 确保已安装 httpx:pip install httpx
  • AsyncClient(app=app, …) 会自动挂载 FastAPI 应用到异步测试环境,无需运行真实服务器;
  • 若仍使用 TestClient,则不应加 await,也不应尝试 async with(它不支持上下文协议),否则会引发 AttributeError;
  • mock.AsyncMock 的返回值需与实际调用链匹配(如 query_fetchone 被 await 调用,因此必须是真正的协程模拟);
  • 原始代码中 response.status_code 在 AsyncClient 中对应 response.status_code(保持兼容),但响应体需用 .json() 或 .text 显式解析。

总结:await 错误的本质是客户端类型不匹配。坚持「同步测试用 TestClient + 普通调用」,「异步测试用 AsyncClient + await」,即可彻底规避此类问题。

https://www.php.cn/faq/2027193.html

发表回复

Your email address will not be published. Required fields are marked *