mock.patch必须作用于被调用位置而非定义位置,因Python导入机制决定需patch代码实际引用的对象路径,如api_client.py中import requests后调用requests.get,则应patch'api_client.requests.get'而非'requests.get'。

mock.patch 为什么必须作用于被调用位置,而不是定义位置
很多人写测试时把 mock.patch 加在第三方库的源文件路径上(比如 @mock.patch('requests.get')),结果 mock 失败——因为 Python 导入机制决定了:你代码里实际调用的是哪个对象,就得 patch 哪个对象的引用位置。比如你在 api_client.py 里写了 import requests; requests.get(...),那就要 patch 'api_client.requests.get',而不是 'requests.get'。
常见错误现象:AssertionError: Expected 'get' to be called once. Called 0 times.
- 检查被测函数中 import 方式:是
import requests还是from requests import get?后者需 patch'api_client.get' - 使用
print(api_client.requests)或断点确认实际绑定的对象模块路径 - 推荐统一用
import requests风格,patch 目标更稳定
如何让 requests.get 抛出 ConnectionError 或 Timeout 异常
第三方接口异常不只是 HTTP 状态码 5xx,更要覆盖网络层失败。直接 raise 异常比返回 error response 更贴近真实故障场景。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 用
side_effect注入异常:mock_get.side_effect = requests.ConnectionError("Network unreachable") - 超时异常要 match requests 实际抛出类型:
side_effect = requests.Timeout("Request timeout"),不是TimeoutError - 若被测逻辑用
except Exception捕获,可传入任意异常;但若显式捕获requests.RequestException,则必须用其子类
示例:
@mock.patch('api_client.requests.get')
def test_api_timeout(mock_get):
mock_get.side_effect = requests.Timeout("slow upstream")
with pytest.raises(requests.Timeout):
fetch_user(123)
mock 返回不同 HTTP 状态码时要注意 response.raise_for_status()
很多代码依赖 response.raise_for_status() 触发异常,这时不能只设 status_code,还必须提供合法的 Response 对象。
直接返回 dict 或 tuple 不会触发 raise_for_status,必须用 requests.Response 实例:
- 用
requests.Response()构造空响应,再手动设置私有属性:resp.status_code = 503,resp._content = b'{"error": "unavailable"}' - 更稳妥的方式是用
requests.models.Response()+mock.Mock()组合,或使用responses库(但那就不是纯 mock 了) - 注意:
raise_for_status()只对 4xx/5xx 调用HTTPError,3xx 默认不抛异常
测试重试逻辑时,mock 如何按顺序返回不同行为
比如代码里用了 tenacity 或手写 while 循环重试三次,第一次超时、第二次连接拒绝、第三次成功——这时候 side_effect 支持列表形式,按调用顺序依次返回。
- 写法:
mock_get.side_effect = [requests.Timeout(), ConnectionRefusedError(), mock_response] - 确保 mock_response 是已配置好
status_code和_content的Response对象 - 如果重试逻辑里 sleep 时间较长,测试会变慢;可在测试前 monkey patch
time.sleep = lambda x: None
容易忽略的一点:多次调用后,mock_get.call_count 必须等于重试次数,否则说明重试没生效或提前退出了。


















