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实例统一管理,避免重复创建:
- 修改
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()
- 在所有需要使用的模块中导入该全局实例:
# 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", } ]
- 测试时直接覆盖全局实例的
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实例:
- 修改业务代码:
# 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()), ): # 业务逻辑 ...
- 测试时覆盖
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
相关产品推荐
相关产品推荐

