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

FastAPI依赖错误无法Mock:健康检查端点测试遇阻

问题:如何正确Mock健康检查依赖的错误场景以覆盖实际逻辑

相关代码

健康检查端点(app/routes/api_v1/status.py)

@router.get("/", response_model=HealthStatus, summary="Database Health Check")
async def db_health_check(_: None = Depends(check_project_db_status_dependency)):
    return HealthStatus(status="Running")

数据库健康检查依赖(app/dependencies.py)

async def check_project_db_status_dependency(session: AsyncSession = Depends(get_project_async_session)) -> None:
    try:
        result = await session.execute(text("SELECT 1"))
        if result.scalar() != 1:
            raise DatabaseNotAccessible(ErrorType.INTERNAL_ERROR.value.DB_NOT_RESPONDING_CORRECTLY)
    except OperationalError as exc:
        raise DatabaseNotAccessible(ErrorType.INTERNAL_ERROR.value.FAILED_TO_CONNECT_TO_DB) from exc

自定义错误处理器

async def database_not_accessible_error_handler(request: Request, exc: DatabaseNotAccessible) -> JSONResponse:
    """
    Handler for DatabaseNotAccessible exception.

    Args:
        request (Request): Request object.
        exc (DatabaseNotAccessible): Exception object.

    Returns:
        JSONResponse: Response object.
    """
    return JSONResponse(
        status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
        content={"detail": str(exc)},
    )

现有测试的问题

目前尝试了两种方式:

  • 依赖覆盖:能得到预期的500状态码,但完全跳过了实际的依赖逻辑,导致代码覆盖率工具标记这部分未覆盖。
  • pytest patch:使用patch.object、mocker.patch.object直接patch依赖路径,均返回200状态码,Mock未生效。

现有依赖覆盖测试代码:

@pytest.mark.parametrize("project_name", PROJECTS)
def test_project_health_check_db_unreachable(
    async_main_session: IAsyncSession,
    project_name: str,
):
    def raise_error():
        raise DatabaseNotAccessible(ErrorType.INTERNAL_ERROR.value.DB_NOT_RESPONDING_CORRECTLY)

    app.dependency_overrides[check_project_db_status_dependency] = raise_error
    response = client.get(build_health_check_url(project_name))
    assert response.status_code == 500
    assert response.json()["detail"] == ErrorType.INTERNAL_ERROR.value.DB_NOT_RESPONDING_CORRECTLY.value


@pytest.mark.parametrize("project_name", PROJECTS)
def test_project_health_check_fail_to_connect(
    async_main_session: IAsyncSession,
    project_name: str,
):
    def raise_error():
        raise DatabaseNotAccessible(ErrorType.INTERNAL_ERROR.value.FAILED_TO_CONNECT_TO_DB)

    app.dependency_overrides[check_project_db_status_dependency] = raise_error
    response = client.get(build_health_check_url(project_name))
    assert response.status_code == 500
    assert response.json()["detail"] == ErrorType.INTERNAL_ERROR.value.FAILED_TO_CONNECT_TO_DB.value

项目结构:

main.py
app/
├── main.py
├── dependencies.py
└── routes/
    └── api_v1/
        └── status.py

正确的Mock方案

要覆盖实际依赖逻辑,需要Mock依赖中数据库会话的行为,而不是直接替换整个依赖。以下是针对两种错误场景的测试代码:

1. 测试数据库连接失败(OperationalError)

MockAsyncSession.execute方法,使其抛出OperationalError,触发依赖中的异常捕获逻辑:

import pytest
from sqlalchemy.exc import OperationalError
from sqlalchemy import text
from app.dependencies import get_project_async_session

@pytest.mark.parametrize("project_name", PROJECTS)
async def test_project_health_check_fail_to_connect(
    mocker,
    project_name: str,
):
    # Mock数据库会话对象
    mock_session = mocker.Mock()
    # 让execute方法抛出OperationalError
    mock_session.execute.side_effect = OperationalError("Connection failed", {}, None)
    # 替换依赖中的get_project_async_session,返回Mock会话
    mocker.patch("app.dependencies.get_project_async_session", return_value=mock_session)

    response = client.get(build_health_check_url(project_name))
    assert response.status_code == 500
    assert response.json()["detail"] == ErrorType.INTERNAL_ERROR.value.FAILED_TO_CONNECT_TO_DB.value
    # 验证execute方法被正确调用
    mock_session.execute.assert_called_once_with(text("SELECT 1"))

2. 测试数据库返回异常结果(SELECT 1 != 1)

Mocksession.execute返回的结果,使其scalar()方法返回非1的值:

import pytest
from sqlalchemy import text
from app.dependencies import get_project_async_session

@pytest.mark.parametrize("project_name", PROJECTS)
async def test_project_health_check_db_unreachable(
    mocker,
    project_name: str,
):
    # Mock会话和执行结果对象
    mock_session = mocker.Mock()
    mock_result = mocker.Mock()
    # 让scalar返回0,触发异常分支
    mock_result.scalar.return_value = 0
    mock_session.execute.return_value = mock_result
    # 替换依赖中的会话获取函数
    mocker.patch("app.dependencies.get_project_async_session", return_value=mock_session)

    response = client.get(build_health_check_url(project_name))
    assert response.status_code == 500
    assert response.json()["detail"] == ErrorType.INTERNAL_ERROR.value.DB_NOT_RESPONDING_CORRECTLY.value
    # 验证execute和scalar方法都被调用
    mock_session.execute.assert_called_once_with(text("SELECT 1"))
    mock_result.scalar.assert_called_once()

关键说明

  • Patch路径正确性:必须patch依赖中实际导入并使用的对象路径(比如app.dependencies.get_project_async_session),而非端点中导入依赖的路径,这是之前Mock失效的核心原因。
  • 保留实际逻辑:通过Mock会话的行为,让依赖代码完整执行,既触发了异常逻辑,又能被覆盖率工具统计到。
  • 异步测试适配:测试函数需定义为async,确保异步Mock和请求执行流程正确。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 13:35:35