Mock objects and non-existent attributes/methods in Python

Last modified: 2019/06/17,1b90ad5

Categories: Python

Updated: Fixed typo in the last paragraph.

Today, I was curious to see this behavior of Mock() objects when using mock:

>>> from mock import Mock
>>> m = Mock()
>>> m.i_dont_exist
<Mock name='mock.i_dont_exist' id='139841609578768'>
>>> m.i_dont_exist()
<Mock name='mock.i_dont_exist()' id='139841609106896'>


The above is expected, since I have not declared a spec when creating the Mock() object, so even when you call a non-existent method or get/set a non-existent attribute, you will not get a AttributeError.

However, I was suprised by the following:

>>> m.assert_not_calledd
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/asaha/.local/share/virtualenvs/606fc8723c1a01b/lib/python2.7/site-packages/mock/mock.py", line 721, in _    _getattr__
raise AttributeError(name)
AttributeError: assert_not_calledd


And the following as well:

>>> m.assert_foo
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/asaha/.local/share/virtualenvs/606fc8723c1a01b/lib/python2.7/site-packages/mock/mock.py", line 721, in __getattr__
raise AttributeError(name)
AttributeError: assert_foo


I guessed that there is likely a check explicitly for non-existent attributes starting with assert, and if it finds so, it will raise a AttributeError. If you look at the __getattr__ method in mock.py, you will see that this is pretty much what is happening. The exact lines are below:

if not self._mock_unsafe: # self._mock_unsafe is by default False
if name.startswith(('assert', 'assret')): # It comes here and an AttributeError is raised
raise AttributeError(name)


This is certainly a good thing, since I have often seen assert_called_once in codebases, and is fairly easy to overlook.