Pytest mocker无法Mock FastAPI路由依赖的Repository实例问题求助
Pytest mocker无法Mock FastAPI路由依赖的Repository实例问题求助
问题描述
我有一个FastAPI项目,在测试路由时遇到了mock不生效的问题:明明用pytest-mocker Mock了Repository实例,但请求还是调用了真实的仓库对象,导致测试断言失败。以下是我的项目结构和代码:
项目结构
/repo /src main.app /routes /resource resource.py /repository repository.py /tests /routes /resource resource_test.py
路由模块(/src/routes/resource/resource.py)
from typing import Annotated from fastapi import APIRouter, Depends from src.repository.repository import Repository, repository router = APIRouter(prefix="/resources") def get_repository() -> Repository: return repository RepositoryDep = Annotated[Repository, Depends(get_repository)] @router.get("/") def list(repo: RepositoryDep) -> list[Resource]: return repo.list()
仓库模块(/src/repository/repository.py)
class Repository: def list(self): # 真实的数据库查询逻辑 ... repository = Repository() # 全局单例实例
测试模块(/tests/routes/resource/resource_test.py)
from fastapi.testclient import TestClient from src.main import app from unittest.mock import MagicMock import pytest from src.repository.repository import Repository client = TestClient(app) def test_get_resources(mocker) -> None: mock_repo = MagicMock(spec=Repository) mock_repo.list.return_value = [ Resource(id=1, name="Something", age=20), Resource(id=2, name="Something2", age=22) ] mocker.patch('src.routes.resource.resource.get_repository', return_value=mock_repo) response = client.get("/resources/") assert response.status_code == 200 content = response.json() assert len(content) == 2 # 断言失败,实际返回真实仓库的数据
问题:为什么我的mock没有生效?该如何修改测试代码?
专家解答
问题根源
你的mock失效核心原因是FastAPI的Depends在模块加载时已经捕获了get_repository函数的原始引用:
- 当路由模块被加载时,
Depends(get_repository)就已经记住了当时的get_repository函数对象; - 后续你用
mocker.patch替换的只是路由模块中get_repository变量的指向,Depends内部保存的依然是最初的函数引用,所以请求处理时还是会调用原始函数,返回真实的Repository实例。
简单说:你patch的是变量名,但Depends用的是变量指向的原始函数对象,两者没有关联。
解决方案
这里提供两种可靠的解决方式,推荐使用第一种FastAPI官方的依赖覆盖方案,更符合框架设计且不易出错。
方案1:使用FastAPI官方的依赖覆盖(推荐)
FastAPI内置了app.dependency_overrides功能,可以直接替换路由的依赖逻辑,完全绕开函数引用的问题:
# 从路由模块导入get_repository函数,用于指定要覆盖的依赖 from src.routes.resource.resource import get_repository def test_get_resources() -> None: mock_repo = MagicMock(spec=Repository) mock_repo.list.return_value = [ Resource(id=1, name="Something", age=20), Resource(id=2, name="Something2", age=22) ] # 覆盖依赖:当需要解析get_repository时,返回mock实例 app.dependency_overrides[get_repository] = lambda: mock_repo response = client.get("/resources/") # 断言逻辑 assert response.status_code == 200 content = response.json() assert len(content) == 2 # 测试后清理覆盖,避免影响其他测试用例 app.dependency_overrides.clear()
这种方式直接告诉FastAPI依赖的替代逻辑,不需要关心原始函数的引用问题,是FastAPI测试的最佳实践。
方案2:调整mocker.patch的目标(适合坚持用mock的场景)
既然你的get_repository函数只是返回路由模块中导入的全局repository单例,那我们可以直接patch这个实例,跳过函数本身:
def test_get_resources(mocker) -> None: mock_repo = MagicMock(spec=Repository) mock_repo.list.return_value = [ Resource(id=1, name="Something", age=20), Resource(id=2, name="Something2", age=22) ] # 直接替换路由模块中引用的repository实例 mocker.patch('src.routes.resource.resource.repository', mock_repo) # 也可以直接替换仓库模块的全局单例: # mocker.patch('src.repository.repository.repository', mock_repo) response = client.get("/resources/") assert response.status_code == 200 content = response.json() assert len(content) == 2
这种方式直接把真实的实例替换成mock,不管get_repository怎么返回,拿到的都是我们的mock对象,也能保证生效。
备注:内容来源于stack exchange,提问作者fedest
相关产品推荐
相关产品推荐

