What's the difference between mock.MagicMock(spec=SomeClass) and mock.create_autospec(SomeClass)?

Viewed 2567

I'm trying to understand the difference between these two mock constructs and when is it appropriate to use either. I tested it in the interpreter, e.g.:

>>> mm = mock.MagicMock(spec=list)
>>> ca = mock.create_autospec(list)
>>> mm
<MagicMock spec='list' id='140372375801232'>
>>> mm()
<MagicMock name='mock()' id='140372384057808'>
>>> mm.append()
<MagicMock name='mock.append()' id='140372375724720'>
>>> mm().append()
<MagicMock name='mock().append()' id='140372375753104'>
>>> ca
<MagicMock spec='list' id='140372384059248'>
>>> ca()
<NonCallableMagicMock name='mock()' spec='list' id='140372384057040'>
>>> ca.append()
<MagicMock name='mock.append()' id='140372375719744'>
>>> ca().append()
<MagicMock name='mock().append()' id='140372375796848'>
>>> 

But I can't understand why "constructing" the mock created using create_autospec gives me a NonCallableMagicMock and the MagicMock gives me more MagicMock. The documentation isn't helping much.

1 Answers

The main difference between using the spec argument and using create_autospec is recursiveness. In the first case, the object itself is specced, while the called object is not:

>>> mm = mock.MagicMock(spec=list)
>>> mm
<MagicMock spec='list' id='2868486557120'>
>>> mm.foo
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "c:\Python\Python38\lib\unittest\mock.py", line 635, in __getattr__
    raise AttributeError("Mock object has no attribute %r" % name)
AttributeError: Mock object has no attribute 'foo'
>>> mm.append
<MagicMock name='mock.append' id='2868486430240'>
>>> mm.append.foo
<MagicMock name='mock.append.foo' id='2868486451408'>

In the second case, the called objects are also specced (lazily):

>>> ca = mock.create_autospec(list)
>>> ca
<MagicMock spec='list' id='2868486254848'>
>>> ca.foo
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "c:\Python\Python38\lib\unittest\mock.py", line 635, in __getattr__
    raise AttributeError("Mock object has no attribute %r" % name)
AttributeError: Mock object has no attribute 'foo'
>>> ca.append
<MagicMock name='mock.append' spec='method_descriptor' id='2868486256336'>
>>> ca.append.foo
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "c:\Python\Python38\lib\unittest\mock.py", line 635, in __getattr__
    raise AttributeError("Mock object has no attribute %r" % name)
AttributeError: Mock object has no attribute 'foo'

There is one caveat, that is shown in your example code. If you use create_autospec as shown here, it behaves as if the object is a class, not an instance, so you are able to call it (creating an instance):

>>> ca = mock.create_autospec(list)
>>> ca()
<NonCallableMagicMock name='mock()' spec='list' id='2868485877280'>

If you want to behave it like an instance, you have to use instance=True:

>>> ca = mock.create_autospec(list, instance=True)
>>> ca
<NonCallableMagicMock spec='list' id='2868485875024'>
>>> ca()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'NonCallableMagicMock' object is not callable

Note that using mock.patch with autospec=True creates a mock that behaves like the one created using mock.create_autospec, as described in the documentation.

Also note that the return value of a call is always a MagicMock, regardless of the return value of the real call. So, even if a function returns None, like list.append, a mock is returned if calling the method from a mock, regardless of the spec.

Related