Mock对象不能直接替换模块里的函数,因为patch作用于“被调用位置”而非“定义位置”,如service.py中from utils import send_email,则必须@patch('service.send_email');否则调用原函数导致mock失效。

Mock对象为什么不能直接替换模块里的函数?
因为 unittest.mock.patch 默认作用域是“被测试函数所在模块的命名空间”,不是定义该函数的模块。比如你写了个 utils.send_email(),在 service.py 里调用它,那 mock 必须打在 service.send_email 上,而不是 utils.send_email —— 否则实际函数照常执行,mock 形同虚设。
常见错误现象:AssertionError: Expected 'send_email' to be called once. Called 0 times.
- 确认被 patch 的路径:是“谁在用”,不是“谁在定义”
- 用
@patch('service.send_email'),而非@patch('utils.send_email') - 如果用上下文管理器,写成
with patch('service.send_email') as mock_send: - 路径字符串必须和 import 后的实际引用路径一致(比如用了
from utils import send_email,那就要 patch'service.send_email')
如何让 mock 返回不同值或抛异常?
return_value 和 side_effect 是核心控制点。前者固定返回,后者可动态响应——包括返回值序列、抛异常、甚至调用真实函数。
使用场景:测试网络请求失败、数据库查询为空、第三方 API 返回不同状态码。
立即学习“Python免费学习笔记(深入)”;
-
mock_fetch.side_effect = [ValueError("timeout"), {"data": "ok"}]→ 第一次调用抛异常,第二次返回字典 -
mock_fetch.side_effect = lambda url: {"api/v1": {"id": 1}}.get(url, {})→ 按输入参数返回不同结果 -
mock_fetch.return_value = None和mock_fetch.return_value = MagicMock()效果完全不同,注意空值与对象行为差异 - 别漏掉
autospec=True:它能校验 mock 调用时的参数名和数量,避免写错mock.call("a", b=1)却没报错
patch 装饰器的参数顺序为什么总出错?
装饰器从下往上生效,mock 对象按这个顺序注入到测试函数参数里 —— 最靠近函数的装饰器对应第一个参数,依此类推。顺序反了就拿不到 mock 实例。
常见错误现象:测试函数签名是 def test_xxx(self, mock_a, mock_b):,但实际 mock_b 是空的,或者报 TypeError: test_xxx() takes 3 positional arguments but 4 were given。
- 写法示例:
@patch('service.notify_user')<br>@patch('service.get_user')<br>def test_notify_on_success(self, mock_get_user, mock_notify):→mock_get_user在前,mock_notify在后 - 如果混用
setUp中的patch.start(),记得在tearDown里stop(),否则 mock 泄漏影响其他测试 - 类级 patch(
@patch.object(...))比模块级更安全,尤其当多个测试共用同一依赖时
assert_called_with 和 assert_called_once_with 有什么区别?
assert_called_with 只检查最后一次调用是否匹配;assert_called_once_with 还额外断言“只调用了一次”。多数业务逻辑要求“恰好一次”,漏掉 once 会导致误判。
性能影响小,但语义差很多:比如重试逻辑里函数被调两次,用 assert_called_with 会通过,而实际应失败。
- 推荐默认用
assert_called_once_with("user@example.com", subject="Welcome") - 查调用历史用
mock_send.call_args_list,它是个Call对象列表,每个元素支持.args和.kwargs - 别用
assert mock_send.called判断是否调用过——它不告诉你参数对不对,容易掩盖深层问题


















