I have a function which takes a list as a parameter. The function is called multiple times and every time some of the list values are updated. The mock object I am using to capture the call arguments, always shows the latest values in the list for all call arguments. The following code shows the problem.
from mock import MagicMock
def multiple_calls_test():
m = MagicMock()
params = [0, 'some_fixed_value', 'some_fixed_value']
for i in xrange(1,10):
params[0] = i
m(params)
for args in m.call_args_list:
print args[0][0]
multiple_calls_test()
And here is the output, Notice all calls have 9 as the first list element.
[9, 'some_fixed_value', 'some_fixed_value']
[9, 'some_fixed_value', 'some_fixed_value']
[9, 'some_fixed_value', 'some_fixed_value']
[9, 'some_fixed_value', 'some_fixed_value']
[9, 'some_fixed_value', 'some_fixed_value']
[9, 'some_fixed_value', 'some_fixed_value']
[9, 'some_fixed_value', 'some_fixed_value']
[9, 'some_fixed_value', 'some_fixed_value']
[9, 'some_fixed_value', 'some_fixed_value']
Is there a way to force the mock object to make a copy of list argument instead of holding the reference to the actual list? Or some other way of asserting the correct value for every method execution? Thanks.
For python 3.8 the accepted solution did not work anymore for me.
However, there is a solution in the official python docs:
https://docs.python.org/3/library/unittest.mock-examples.html#coping-with-mutable-arguments
You have to scroll down a bit to find the following:
An alternative approach is to create a subclass of Mock or MagicMock that copies (using copy.deepcopy()) the arguments. Here’s an example implementation:
from copy import deepcopy
class CopyingMock(MagicMock):
def __call__(self, /, *args, **kwargs):
args = deepcopy(args)
kwargs = deepcopy(kwargs)
return super(CopyingMock, self).__call__(*args, **kwargs)
This worked for me for python 3.8.
Unfortunately, this looks to be a shortcoming of the mock
library, and from looking at the code this doesn't look to be possible without patching the mock library itself. However, it looks like there is a fairly lightweight way to do this to get the effect you are looking for:
import copy
from mock import MagicMock
class CopyArgsMagicMock(MagicMock):
"""
Overrides MagicMock so that we store copies of arguments passed into calls to the
mock object, instead of storing references to the original argument objects.
"""
def _mock_call(_mock_self, *args, **kwargs):
args_copy = copy.deepcopy(args)
kwargs_copy = copy.deepcopy(kwargs)
return super(CopyArgsMagicMock, self)._mock_call(*args_copy, **kwargs_copy)
Then (to state the obvious) simply replace your MagicMock
with a CopyArgsMagicMock
and you should see the required behavior.
Please note that this has only been tested for the use case provided, so this may not be a complete and robust solution to the problem, but hopefully it proves useful.
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