Lon*_*ner 5 python mocking assertion python-unittest python-3.7
代码:
from unittest.mock import MagicMock, call
mm = MagicMock()
mm().foo()['bar']
print(mm.mock_calls)
print()
mm.assert_has_calls([call(), call().foo(), call().foo().__getitem__('bar')])
Run Code Online (Sandbox Code Playgroud)
输出:
[call(), call().foo(), call().foo().__getitem__('bar')]
Traceback (most recent call last):
File "foo.py", line 9, in <module>
mm.assert_has_calls([call(), call().foo(), call().foo().__getitem__('bar')])
TypeError: tuple indices must be integers or slices, not str
Run Code Online (Sandbox Code Playgroud)
如何修复这个断言?
这是一个错误,因为您应该始终能够使用对象的repr输出call来重新创建具有call相同值的新对象。
这里的问题是call, 的一个实例unittest.mock._Call依赖于__getattr__方法来实现其链式调用注释魔法,_Call当给出一个不存在的属性名称时,返回另一个对象。但是由于_Call是 的子类tuple,它确实定义了__getitem__属性,因此当请求属性时,该_Call.__getattr__方法将简单地返回tuple.__getitem__而不是_Call对象__getitem__。由于tuple.__getitem__不接受字符串作为参数,因此您会收到上述错误。
为了解决这个问题,由于确定是否定义了属性是通过调用__getattribute__方法完成的,AttributeError当找不到给定的属性名称时会引发,我们可以重写,_Call.__getattribute__以便在给定的属性时引发这样的异常name is '__getitem__',有效地使__getitem__“不存在”并将其解析传递给该__getattr__方法,然后该方法将返回一个_Call对象,就像任何其他不存在的属性一样:
def __getattribute__(self, attr):
if attr == '__getitem__':
raise AttributeError
return tuple.__getattribute__(self, attr)
call.__class__.__getattribute__ = __getattribute__ # call.__class__ is _Call
Run Code Online (Sandbox Code Playgroud)
以便:
mm = MagicMock()
mm().foo()['bar']
mm.assert_has_calls([call(), call().foo(), call().foo().__getitem__('bar')])
Run Code Online (Sandbox Code Playgroud)
不会引发任何异常,而:
mm.assert_has_calls([call(), call().foo(), call().foo().__getitem__('foo')])
Run Code Online (Sandbox Code Playgroud)
会提出:
AssertionError: Calls not found.
Expected: [call(), call().foo(), call().foo().__getitem__('foo')]
Actual: [call(), call().foo(), call().foo().__getitem__('bar')]
Run Code Online (Sandbox Code Playgroud)
演示:https : //repl.it/repls/StrikingRedundantAngle
请注意,我已将错误提交到Python 错误跟踪器并将我的修复作为拉取请求提交给 CPython,因此希望您在不久的将来不再需要执行上述操作。