Code:
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')])
Output:
[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
How to fix this assert?
This is a bug, since you should always be able to use the repr
output of a call
object to re-create a new call
object of the same value.
The problem here is that call
, an instance of unittest.mock._Call
, relies on the __getattr__
method to implement its chained call annotation magic, where another _Call
object is returned when a non-existent attribute name is given. But since _Call
is a subclass of tuple
, which does define the __getitem__
attribue, the _Call.__getattr__
method would simply return tuple.__getitem__
instead of a _Call
object when the attribute __getitem__
is asked for. Since tuple.__getitem__
does not accept a string as a parameter, you get the said error as a result.
To fix this, since the determination of whether or not an attribute is defined is done via the call of the __getattribute__
method, which raises AttributeError
when a given attribute name is not found, we can override _Call.__getattribute__
so that it would raise such an exception when the given attribute name is '__getitem__'
, to effectively make __getitem__
"non-existent" and pass on its resolution to the __getattr__
method, which would then return a _Call
object just like it would for any other non-existent attribute:
def __getattribute__(self, attr):
if attr == '__getitem__':
raise AttributeError
return tuple.__getattribute__(self, attr)
call.__class__.__getattribute__ = __getattribute__ # call.__class__ is _Call
so that:
mm = MagicMock()
mm().foo()['bar']
mm.assert_has_calls([call(), call().foo(), call().foo().__getitem__('bar')])
would raise no exception, while:
mm.assert_has_calls([call(), call().foo(), call().foo().__getitem__('foo')])
would raise:
AssertionError: Calls not found.
Expected: [call(), call().foo(), call().foo().__getitem__('foo')]
Actual: [call(), call().foo(), call().foo().__getitem__('bar')]
Demo: https://repl.it/repls/StrikingRedundantAngle
Note that I have filed the bug at the Python bug tracker and submitted my fix as a pull request to CPython, so hopefully you will no longer have to do the above in the near future.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.