'Use a MagicMock as its own return value?

Normally, a MagicMock returns new MagicMocks for any attribute or method called on it:

>>> mm = MagicMock()
>>> mm
<MagicMock id='140094558921616'>
>>> mm()
<MagicMock name='mock()' id='140094551215016'>
>>> mm.foo()
<MagicMock name='mock.foo()' id='140094551605656'>

Lately, I've been writing fixtures that instead return the same mock for all methods and attributes on the mocked class:

>>> mm = MagicMock()
>>> mm.return_value = mm
>>> mm.foo.return_value = mm
>>> mm
<MagicMock id='140094551638984'>
>>> mm()
<MagicMock id='140094551638984'>
>>> mm.foo()
<MagicMock id='140094551638984'>

This has been convenient, as my fixture can simply yield this single mock and multiple assertions can be run against it. I have not seen any downsides: the called,call_count, and call_args attributes continue to work accurately for each of the mocked attributes.

However, the fact that MagicMock doesn't work this way out of the box makes me feel like I might be missing something.

Is this a bad idea?



Solution 1:[1]

The reason that MagicMock doesn’t work that way out of the box is that tests could no longer distinguish between calls to the mock itself, and calls to the mock’s return value. Here’s the mock behaving as designed: we can distinguish that the mock itself was called with the argument 5, and then the object it returned was called with the argument 6.

from unittest.mock import MagicMock
m = MagicMock()

r = m(5)
r(6)

print(m.call_args_list)
print(r.call_args_list)

Output:

[call(5)]
[call(6)]

If we instead make the mock its own return value, then the tests can no longer distinguish between the two. The test can see that two calls were made and can see the two arguments, but cannot tell whether the calls were made to m itself or to the first call’s return value r.

from unittest.mock import MagicMock
m = MagicMock()
m.return_value = m

r = m(5)
r(6)

print(m.call_args_list)
print(r.call_args_list)

Output:

[call(5), call(6)]
[call(5), call(6)]

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Brandon Rhodes