如何在Pytest中Mock FastAPI Depends?直接调用端点失效问题解决
在pytest中Mock FastAPI Depends函数的解决方案
问题描述
使用starlette.testclient.TestClient访问接口时,通过app.dependency_overrides[get_user] = ...覆盖依赖有效,但直接调用接口方法时失效。尝试Mock Depends本身也未成功。核心问题是代码深处存在直接调用FastAPI端点的非推荐写法,但不想大规模重构,希望通过Mock手段解决测试失败问题。
代码复现
from fastapi import Depends, FastAPI, Request from starlette.testclient import TestClient app = FastAPI() def get_user(request: Request): # 实际场景从请求头提取用户ID # request.headers.get(...) return "me" @app.get("/one") async def endpoint_one(user_id=Depends(get_user)): return {"user_id": user_id} @app.get("/two") async def endpoint_two(user_id=Depends(get_user)): result = await endpoint_one() # 问题核心:直接调用端点未传入依赖参数 return result # 测试通过 def test_one(): client = TestClient(app) result = client.get("/one") assert result.json()["user_id"] == "me" # 测试失败:返回结果为{'dependency': {}, 'use_cache': True} def test_two(): client = TestClient(app) result = client.get("/two") assert result.json()["user_id"] == "me"
解决方案
方法1:手动注入请求上下文
直接调用端点时,需要确保请求上下文存在。可以构建模拟请求并绑定到FastAPI的请求上下文变量:
from starlette.requests import Request as StarletteRequest from starlette.datastructures import Headers from contextvars import copy_context import asyncio def mock_get_user(request: Request): return "mock_user" def test_two_fix_context(): # 覆盖依赖供TestClient使用 app.dependency_overrides[get_user] = mock_get_user # 构建模拟请求对象 mock_request = StarletteRequest( scope={ "type": "http", "method": "GET", "path": "/two", "headers": Headers().raw, }, receive=lambda: asyncio.Future() ) # 将请求注入上下文 ctx = copy_context() ctx[Request.__request_ctx_key__] = mock_request # 在上下文环境中执行测试请求 client = TestClient(app) result = ctx.run(lambda: client.get("/two")) assert result.json()["user_id"] == "mock_user" # 清理依赖覆盖 app.dependency_overrides.clear()
方法2:Mock端点的默认参数值
针对直接调用端点的场景,可以通过Mock端点函数的默认参数,绕过Depends的解析逻辑:
from unittest.mock import patch def test_two_mock_defaults(): # 覆盖依赖供TestClient使用 app.dependency_overrides[get_user] = lambda: "mock_user" # Mock endpoint_one的user_id默认值 with patch.object(endpoint_one, '__defaults__', ("mock_user",)): client = TestClient(app) result = client.get("/two") assert result.json()["user_id"] == "mock_user" # 清理依赖覆盖 app.dependency_overrides.clear()
方法3:抽离核心逻辑(最小侵入式重构)
如果允许做微小代码调整,建议把端点的业务逻辑抽离成独立函数,避免直接调用端点:
# 重构后的代码 async def fetch_user_response(user_id): return {"user_id": user_id} @app.get("/one") async def endpoint_one(user_id=Depends(get_user)): return await fetch_user_response(user_id) @app.get("/two") async def endpoint_two(user_id=Depends(get_user)): # 调用逻辑函数而非端点 result = await fetch_user_response(user_id) return result
重构后,无论通过TestClient访问还是直接调用逻辑函数,依赖覆盖都能正常生效,测试也能顺利通过。
内容的提问来源于stack exchange,提问作者HeyMan
相关产品推荐
相关产品推荐

