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

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函数的原始引用:

  1. 当路由模块被加载时,Depends(get_repository)就已经记住了当时的get_repository函数对象;
  2. 后续你用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.14 11:54:33