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

FastAPI中同时使用Depends与Security的端点测试问题

解决FastAPI同时使用Depends和Security时的测试依赖覆盖问题

问题根源

核心问题是重复实例化Verification类:

  • main.py和api_v1/endpoints/exchange_rates.py各自创建了独立的Verification实例,两个实例的verify方法是完全不同的对象。
  • 测试时你覆盖的是exchange_rates.py中实例的verify,但FastAPI注册路由时绑定的是模块加载时创建的实例方法,看似导入了正确实例,实则因模块加载顺序或FastAPI内部依赖追踪机制导致覆盖未生效;mock类方法的方式也无效,因为已创建的实例不会继承类层面的补丁。

解决方案

方案1:统一全局Verification实例(推荐)

将Verification实例统一管理,避免重复创建:

  1. 修改authentication/verification.py,创建全局实例并导出:
from fastapi import HTTPException, status

class Verification:
    def verify(self):
        # 原认证逻辑
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Not authenticated"
        )

# 全局唯一实例
security = Verification()
  1. 在所有需要使用的模块中导入该全局实例:
# main.py
from authentication.verification import security

@app.get("/")
async def root(auth_result: str = Security(security.verify)):
    return {"message": "Hello World this is restapi.py!"}
# api_v1/endpoints/exchange_rates.py
from authentication.verification import security
from fastapi import APIRouter, Depends, Security
from ...schemas import ExchangeRatesQuery

router = APIRouter()

@router.get("/")
async def exchange_rates(
    query: ExchangeRatesQuery = Depends(),
    auth_result: str = Security(security.verify),
):
    # 业务逻辑
    return [
        {
            "exchange_date": "2023-03-20",
            "exchange_rate": "0.88",
            "base_currency": "USD",
            "target_currency": "EUR",
        }
    ]
  1. 测试时直接覆盖全局实例的verify方法:
from fastapi.testclient import TestClient
from restapi.main import app
from authentication.verification import security

def test_exchange_rates():
    client = TestClient(app)
    # 覆盖全局实例的verify方法
    app.dependency_overrides[security.verify] = lambda: ""
    
    response = client.get(
        "/api/v1/exchange-rates?start_date=2023-03-20&end_date=2023-03-21&base_currency=USD&target_currencies=EUR",
    )
    
    assert response.status_code == 200
    assert response.json() == [
        {
            "exchange_date": "2023-03-20",
            "exchange_rate": "0.88",
            "base_currency": "USD",
            "target_currency": "EUR",
        }
    ]
    
    # 清理覆盖,避免影响其他测试
    app.dependency_overrides.pop(security.verify, None)

方案2:精准patch路由绑定的实例方法

如果无法统一全局实例,直接针对exchange_rates.py中创建的具体实例打补丁:

from fastapi.testclient import TestClient
from unittest import mock
from restapi.main import app
from restapi.api_v1.endpoints.exchange_rates import security

def test_exchange_rates():
    # 直接patch路由使用的实例的verify方法
    with mock.patch.object(security, "verify", return_value=""):
        client = TestClient(app)
        response = client.get(
            "/api/v1/exchange-rates?start_date=2023-03-20&end_date=2023-03-21&base_currency=USD&target_currencies=EUR",
        )
        
        assert response.status_code == 200
        assert response.json() == [
            {
                "exchange_date": "2023-03-20",
                "exchange_rate": "0.88",
                "base_currency": "USD",
                "target_currency": "EUR",
            }
        ]

这种方式无需修改业务代码,直接针对测试目标实例打补丁,确保覆盖到路由实际使用的方法。

方案3:通过依赖注入获取实例

如果需要灵活的实例管理(如多环境配置),可以通过Depends注入Verification实例:

  1. 修改业务代码:
# exchange_rates.py
from fastapi import APIRouter, Depends, Security
from authentication.verification import Verification
from ...schemas import ExchangeRatesQuery

router = APIRouter()

# 定义获取Verification实例的依赖
def get_security():
    return Verification()

@router.get("/")
async def exchange_rates(
    query: ExchangeRatesQuery = Depends(),
    security: Verification = Depends(get_security),
    auth_result: str = Security(lambda: security.verify()),
):
    # 业务逻辑
    ...
  1. 测试时覆盖get_security依赖:
from fastapi.testclient import TestClient
from unittest.mock import Mock
from restapi.main import app
from restapi.api_v1.endpoints.exchange_rates import get_security

def test_exchange_rates():
    client = TestClient(app)
    # 创建mock实例
    mock_security = Mock()
    mock_security.verify.return_value = ""
    # 覆盖依赖获取函数
    app.dependency_overrides[get_security] = lambda: mock_security
    
    response = client.get(...)
    assert response.status_code == 200
    
    # 清理覆盖
    app.dependency_overrides.pop(get_security, None)

关键注意事项

  • 避免在多个模块重复创建同一依赖类的实例,否则每个实例的方法都是独立对象,依赖覆盖会失效。
  • 使用mock.patch.object时,必须针对路由实际使用的具体实例打补丁,类补丁仅对后续创建的实例生效。
  • 测试后务必清理app.dependency_overrides,避免影响其他测试用例。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 17:39:49