Python unittest Mock的assert_has_calls返回其他mock调用问题求解
问题原因
unittest.mock.MagicMock的assert_has_calls断言基于Mock对象的method_calls属性实现,该属性会递归记录所有子Mock对象的方法调用,子调用会以call().子方法名(参数)的格式被追加到父Mock的调用列表中。
你两次调用do_foo()的完整调用链记录如下:
- 调用
bar_supplier.get_bar()→ 记录为call() - 调用返回的Bar实例Mock的
do_a_thing()→ 记录为call().do_a_thing() - 第二次调用
bar_supplier.get_bar()→ 记录为call() - 第二次调用返回的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
相关产品推荐
相关产品推荐

