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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With