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

FastAPI单元测试:如何Mock ServiceNow客户端避免真实API调用

FastAPI单元测试Mock ServiceNow客户端失败问题解决

问题描述

我正在为FastAPI项目编写单元测试,其中一个接口用于获取ServiceNow工单。待测试的接口代码如下:

from aiosnow.models.table.declared import IncidentModel as Incident
from fastapi import APIRouter

router = APIRouter()
@router.post("/get_ticket")
async def snow_get_ticket(req: DialogflowRequest):
    """Retrieves the status of the ticket in the parameter."""

    client = create_snow_client(
        SNOW_TEST_CONFIG.servicenow_url, SNOW_TEST_CONFIG.user, SNOW_TEST_CONFIG.pwd
    )

    params: dict = req.sessionInfo["parameters"]
    ticket_num = params["ticket_num"]

    try:
        async with Incident(client, table_name="incident") as incident:
            response = await incident.get_one(Incident.number == ticket_num)
            stage_value = response.data["state"].value
            desc = response.data["description"]

 # ... 其他数据处理逻辑,无关部分省略

目前遇到的问题是单元测试无法正确Mock客户端响应,每次都会触发真实的API调用。我写的测试代码如下:

from fastapi.testclient import TestClient
client = TestClient(app)

@patch("aiosnow.models.table.declared.IncidentModel")
def test_get_ticket_endpoint_valid_ticket_num(self, mock_client):
    mock_client.return_value = {"data" : {"state": "new",
                                      "description": "test"}} 
    
    response = client.post(
        "/snow/get_ticket", json=json.load(self.test_request)
    )
    assert response.status_code == 200

我怀疑Mock了错误的对象,但不确定应该Mock哪个。

解决方案

你的问题核心在于三个点:Mock路径错误、未正确模拟异步上下文管理器、返回值结构不匹配代码的访问逻辑。以下是具体修正步骤:

1. 修正Mock路径

unittest.mock.patch需要Mock被测试代码实际导入使用的对象路径,而非原模块的路径。假设你的路由代码文件路径是app/routes/snow.py,那么你应该Mockapp.routes.snow.Incident(也就是代码里用as Incident导入的别名对象),而不是原模块的aiosnow.models.table.declared.IncidentModel。

2. 模拟异步上下文管理器

代码中使用了async with Incident(...) as incident:,因此需要模拟Incident类的__aenter__方法,让它返回一个包含get_one方法的Mock对象,才能正确适配异步上下文的逻辑。

3. 匹配返回值结构

代码里访问的是response.data["state"].value,说明state是一个带有value属性的对象,而非直接的字符串。你之前直接返回字典的方式不符合这个结构,需要模拟出对应的层级和属性。

修正后的测试代码示例:

from unittest.mock import patch, AsyncMock
from fastapi.testclient import TestClient
client = TestClient(app)

def test_get_ticket_endpoint_valid_ticket_num(self):
    # 模拟符合代码访问结构的返回值
    mock_state = type('MockState', (), {'value': 'new'})()
    mock_response = type('MockResponse', (), {
        'data': {
            'state': mock_state,
            'description': 'test'
        }
    })()

    # 模拟Incident的异步上下文和get_one方法
    with patch("你的路由文件路径.Incident") as mock_incident_cls:
        # 让async with返回的incident对象具备get_one方法
        mock_incident = AsyncMock()
        mock_incident.get_one.return_value = mock_response
        mock_incident_cls.return_value.__aenter__.return_value = mock_incident

        # 发送测试请求
        response = client.post(
            "/snow/get_ticket", json=json.load(self.test_request)
        )

        # 断言状态码
        assert response.status_code == 200
        # 可选:断言get_one是否被正确调用
        mock_incident.get_one.assert_called_once()

关键注意点

  • 将你的路由文件路径替换为实际路径,例如路由代码在app/api/snow_routes.py,则写app.api.snow_routes.Incident
  • 使用AsyncMock处理异步方法,避免触发真实的API请求
  • 严格匹配代码中对返回值的访问结构,确保Mock的数据能被正常解析

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 02:15:38