Question

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.

Était-ce utile?

La solution

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.

Autres conseils

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.

Try m.mock_calls. This lists all the calls that were made. I think this should work:

>>> from unittest.mock import MagicMock, call
>>> m = MagicMock()
>>> m('abc')
<MagicMock name='mock()' id='2634881401576'>
>>> m('def')
<MagicMock name='mock()' id='2634881401576'>
>>> call('abc') in m.mock_calls
True
>>> call('ghi') in m.mock_calls
False
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top