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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 10:01:21