FastAPI单元测试:如何用预定义响应替换远程API返回
FastAPI单元测试替换第三方API响应的实现方案
完全可以在测试场景下用预定义模拟响应替换真实远程调用结果,这是后端接口单元测试隔离外部依赖的标准实践,不需要发起真实外网请求,还能覆盖各类正常、异常返回场景。
方案1:零业务代码改动,通过mock打桩替换远程调用
你现有代码不需要做任何调整,直接用Python标准库unittest.mock(搭配pytest使用更便捷)拦截远程调用逻辑,返回预设响应即可,支持单次请求触发多次远程调用的场景。
核心实现逻辑
你封装的call_api方法底层用requests.get发起请求,只需要在测试时把requests.get(或者你封装的API.call_api方法)替换为mock对象,就能完全控制返回结果,还能校验调用参数、调用次数是否符合预期。
示例代码
from fastapi.testclient import TestClient from unittest.mock import Mock, patch from web_api import app client = TestClient(app) def test_stuff_interface(): # 构造和真实requests.Response行为一致的模拟响应 mock_resp_success1 = Mock() mock_resp_success1.status_code = 200 mock_resp_success1.json.return_value = {"things": [{"id": 1, "name": "item1"}, {"id": 2, "name": "item2"}]} mock_resp_success1.raise_for_status = Mock() # 不抛异常代表请求成功 mock_resp_success2 = Mock() mock_resp_success2.status_code = 200 mock_resp_success2.json.return_value = {"things": [{"id": 3, "name": "item3"}]} mock_resp_success2.raise_for_status = Mock() # patch路径必须写业务代码中实际引用requests的模块路径,否则mock不生效 # 比如你的API类定义在web_api.api_client模块,路径就写"web_api.api_client.requests.get" with patch("web_api.requests.get") as mock_get: # 多次调用场景下,side_effect传入列表可按调用顺序返回不同响应 # 也可以传入函数,根据url、params参数动态返回对应结果 mock_get.side_effect = [mock_resp_success1, mock_resp_success2] # 发起对自有接口的请求 resp = client.get("/stuff/", params={"param1": "test"}) # 校验自有接口返回符合预期 assert resp.status_code == 200 # 补充你自己的业务返回断言,比如返回的item数量、字段值等 # 可选:校验远程调用逻辑是否正确 assert mock_get.call_count == 2 # 验证确实触发了2次远程调用 first_call_url = mock_get.call_args_list[0][0][0] assert "something" in first_call_url # 验证请求endpoint拼接正确
如果要模拟第三方接口报错场景,只需要让mock对象的raise_for_status抛出对应异常即可:
from requests.exceptions import HTTPError mock_resp_error = Mock() mock_resp_error.status_code = 500 mock_resp_error.raise_for_status.side_effect = HTTPError("第三方服务异常")
方案2:mock自定义API类方法,粒度更贴合业务
如果觉得直接mockrequests层太底层,可以直接拦截你自己封装的API.call_api方法,不需要关心requests的底层细节,写法更简洁:
def test_stuff_interface_mock_api_method(): with patch("web_api.API.call_api") as mock_call_api: mock_resp = Mock() mock_resp.status_code = 200 mock_resp.json.return_value = {"things": [{"id": 1, "name": "test_item"}]} mock_resp.raise_for_status = Mock() mock_call_api.return_value = mock_resp resp = client.get("/stuff/", params={"test": "value"}) assert resp.status_code == 200 # 补充业务断言
实践注意点
- 注意patch路径正确性:必须mock业务代码中实际使用的对象引用路径,而不是第三方库本身的路径,这是mock不生效的最常见原因。
- 公共mock逻辑抽成fixture:如果多个测试用例都需要模拟第三方API,可以用pytest的fixture封装mock逻辑,减少重复代码。
- 覆盖全部分支场景:通过mock可以自由构造空返回、字段缺失、权限错误、服务不可用等各类极端响应,覆盖自有代码的所有异常处理分支,这是调用真实第三方接口无法稳定做到的。
- 不要在单元测试中发起真实第三方请求:网络波动、第三方服务限流/宕机、测试数据污染都会导致测试结果不稳定,违背单元测试隔离依赖的原则。
内容的提问来源于stack exchange,提问作者vinalti
相关产品推荐
相关产品推荐

