使用mock.patch(autospec=True)替换side_effect为异常时触发TypeError
问题
我想用mock.patch装饰器给包含多个测试的类设置默认side_effect,然后在部分测试里替换成其他行为。但当我尝试替换成Exception时,mock抛出了TypeError: 'Exception' object is not iterable。
注意:这个问题只在装饰器里用autospec=True时才会出现。移除这个参数或者换成spec=my_original_function后一切正常。这是mock模块的Bug吗?我用的版本是1.1.0。
复现代码:
from unittest import TestCase from mock import patch def my_original_function(): return 'my_original_function' def my_default_side_effect(): return 'my_default_side_effect' @patch('tests.my_test.my_original_function', side_effect=my_default_side_effect, autospec=True) class DebugTest(TestCase): def test_default_side_effect_works(self, my_patch): # 正常运行 self.assertEqual(my_patch(), 'my_default_side_effect') def test_default_side_effect_can_be_replaced_with_new_function(self, my_patch): # 正常运行 my_patch.side_effect = lambda: 'some_other_side_effect' self.assertEqual(my_patch(), 'some_other_side_effect') def test_default_side_effect_can_be_replaced_with_new_list(self, my_patch): # 正常运行 my_patch.side_effect = ['other_side_effect_1', 'other_side_effect_2'] self.assertEqual(my_patch(), 'other_side_effect_1') self.assertEqual(my_patch(), 'other_side_effect_2') def test_default_side_effect_can_be_replaced_with_new_exception(self, my_patch): # 运行失败 my_patch.side_effect = Exception('exception_side_effect') with self.assertRaisesRegex(Exception, 'exception_side_effect'): my_patch() # 报错:AssertionError: "exception_side_effect" does not match "'Exception' object is not iterable"
解决方案
这不是mock的Bug,是autospec=True下的特殊行为导致的:
当使用autospec=True时,生成的mock对象会严格遵循原函数的接口,同时在处理side_effect时,会优先判断赋值的对象是否为可迭代类型(函数/方法除外)。当你直接赋值一个Exception实例时,mock会把它当成可迭代对象尝试遍历,但Exception本身不可迭代,因此抛出TypeError。
修复方法有两种:
将Exception实例放入列表中
mock会依次返回列表中的元素,如果元素是Exception实例,就会抛出该异常:def test_default_side_effect_can_be_replaced_with_new_exception(self, my_patch): my_patch.side_effect = [Exception('exception_side_effect')] with self.assertRaisesRegex(Exception, 'exception_side_effect'): my_patch()用函数来抛出异常
给side_effect赋值一个函数,函数内部主动抛出异常:def test_default_side_effect_can_be_replaced_with_new_exception(self, my_patch): def raise_my_exception(): raise Exception('exception_side_effect') my_patch.side_effect = raise_my_exception with self.assertRaisesRegex(Exception, 'exception_side_effect'): my_patch()
另外,建议升级mock版本到2.0以上,或者直接使用Python 3.3+自带的unittest.mock模块,新版本中已经优化了side_effect对Exception实例的处理逻辑,无需额外处理即可直接赋值。
内容的提问来源于stack exchange,提问作者Nahuel Aguilar
相关产品推荐
相关产品推荐

