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

Python unittest Mock的assert_has_calls返回其他mock调用问题求解

问题原因

unittest.mock.MagicMock的assert_has_calls断言基于Mock对象的method_calls属性实现,该属性会递归记录所有子Mock对象的方法调用,子调用会以call().子方法名(参数)的格式被追加到父Mock的调用列表中。

你两次调用do_foo()的完整调用链记录如下:

  1. 调用bar_supplier.get_bar() → 记录为call()
  2. 调用返回的Bar实例Mock的do_a_thing() → 记录为call().do_a_thing()
  3. 第二次调用bar_supplier.get_bar() → 记录为call()
  4. 第二次调用返回的Bar实例Mock的do_a_thing() → 记录为call().do_a_thing()

这也解释了为什么第一个测试用例可以通过:你只需要匹配一次call(),实际调用列表的第一个条目刚好符合要求,因此断言通过;第二个测试你需要连续匹配两次call(),但两次call()中间夹了call().do_a_thing()的记录,无法按顺序匹配,因此报错。

解决方案

你可以根据需求选择以下任意一种方案解决问题:

方案1:直接检查方法的call_args_list属性

每个Mock方法的call_args_list属性只会记录当前方法自身的调用参数,不会包含任何子Mock的调用记录,完全符合你的需求。把第二个测试用例的断言修改为:

self.assertEqual(bar_supplier.get_bar.call_args_list, [call(), call()])

方案2:给assert_has_calls添加any_order=True参数

如果你不需要严格校验get_bar两次调用的前后顺序,只需要确认方法确实被调用了两次,可以添加该参数忽略调用的前后顺序:

bar_supplier.get_bar.assert_has_calls([call(), call()], any_order=True)

方案3:切断子Mock和父Mock的关联

你可以显式设置get_bar返回的Mock对象的parent属性为None,这样子Mock的调用记录就不会被同步到父Mock的调用列表中:

# 替换原来的 bar_supplier.get_bar.return_value = MagicMock()
bar_supplier.get_bar.side_effect = lambda: MagicMock(parent=None)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 09:12:03