Last active
August 10, 2023 10:11
-
-
Save slinkp/c3ec1f47d7ecfe682ad4 to your computer and use it in GitHub Desktop.
How to use Mock Specs Properly with Classes
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
TL/DR: | |
Never instantiate mock.Mock() directly. | |
Instead use either mock.create_autospec(YourClass) OR mock.patch('YourClass', autospec=True). | |
The "spec" feature of Mock is great, it helps avoid your mocked API drifting out of sync with your real API. | |
But there is a gotcha if you are mocking classes directly - it's easy to mock the class but you need to | |
ensure the spec applies to the *instance* as well. | |
""" | |
>>> import mock | |
>>> class Foo(object): | |
... def bar(self): pass | |
... | |
>>> WrongMockFoo = mock.Mock(spec=Foo) | |
>>> WrongMockFoo.bar() | |
<Mock name='mock.bar()' id='17171024'> | |
>>> WrongMockFoo.other() # This should fail... and it does! | |
Traceback (most recent call last): | |
File "<stdin>", line 1, in <module> | |
File "/usr/local/lib/python2.7/dist-packages/mock.py", line 658, in __getattr__ | |
raise AttributeError("Mock object has no attribute %r" % name) | |
AttributeError: Mock object has no attribute 'other' | |
""" | |
That's great, but what about instances of the mocked class? | |
""" | |
>>> foo = WrongMockFoo() | |
>>> foo.bar() | |
<Mock name='mock().bar()' id='17171408'> | |
>>> foo.other() # uh-oh, this should fail, but the instance doesn't inherit the class spec | |
<Mock name='mock().other()' id='17171664'> | |
""" | |
To fix this so instances work properly, use create_autospec(). | |
""" | |
>>> BetterMockFoo = mock.create_autospec(Foo) | |
>>> foo = BetterMockFoo() | |
>>> foo.bar() | |
<MagicMock name='mock().bar()' id='17171792'> | |
>>> foo.other() | |
Traceback (most recent call last): | |
File "<stdin>", line 1, in <module> | |
File "/usr/local/lib/python2.7/dist-packages/mock.py", line 658, in __getattr__ | |
raise AttributeError("Mock object has no attribute %r" % name) | |
AttributeError: Mock object has no attribute 'other' | |
""" | |
And if you're using mock.patch(), just pass autospec=True. | |
""" | |
>>> with mock.patch('__main__.Foo', autospec=True) as AlsoBetterMockFoo: | |
... foo = AlsoBetterMockFoo() | |
... print foo.bar() | |
... print foo.other() # this should fail! | |
... | |
<MagicMock name='Foo().bar()' id='17136656'> | |
Traceback (most recent call last): | |
File "<stdin>", line 4, in <module> | |
File "/usr/local/lib/python2.7/dist-packages/mock.py", line 658, in __getattr__ | |
raise AttributeError("Mock object has no attribute %r" % name) | |
AttributeError: Mock object has no attribute 'other' | |
""" | |
Speccing the mock also helps catch call signature errors. | |
""" | |
>>> foo.bar('uhoh') | |
Traceback (most recent call last): | |
File "<stdin>", line 1, in <module> | |
File "/usr/local/lib/python2.7/dist-packages/mock.py", line 954, in __call__ | |
_mock_self._mock_check_sig(*args, **kwargs) | |
TypeError: <lambda>() takes exactly 1 argument (2 given) | |
>>> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks for introducing the
create_autospec
, the examples are clear and helpful. :)