When should I use autospec=True
when using mock.patch
and its variants?
On one hand, this article warns us to always use autospec=True
:
... you should always use the
create_autospec
method and theautospec
parameter with the@patch
and@patch.object
decorators.
On the other hand, autospec
has serious drawbacks and limits, as explained in idjaw's answer to this question.
So my question is: when should I use autospec=True
or create_autospec
, and when should I not use it?
I fear not using autospec
may result in tests not breaking when they really should break, as described in the mentioned article. However autospec
has its drawbacks. How should I act?
I can understand the motivation to suggest the enforcing of using autospec
.
Maybe the following could help provide more clarity on what you get and don't get with autospec.
In short, using autospec ensures that the attributes you use in your mock are in fact part of the class you are mocking.
So, with the example below, I'll illustrate how a test will pass when technically you might not want it to:
Take this simple example we will test:
class Foo:
def __init__(self, x):
self.x = x
class Bar:
def __init__(self):
self.y = 4
self.c = Foo('potato')
And the test code:
class TestAutoSpec(unittest.TestCase):
@patch('some_module.Foo')
def test_autospec(self, mock_foo_class):
mock_foo_obj = mock_foo_class.return_value
bar_obj = some_module.Bar()
self.assertTrue(hasattr(bar_obj.c, 'you_should_fail'))
Now, if you look back at the Foo
class, you will clearly see you_should_fail
is clearly not an attribute in Foo
. However, if you run this test code, it will in fact pass. Which is very misleading.
This is because if an attribute does not exist in a MagicMock
, it will still be of type MagicMock
. If you print type(bar_obj.c.you_should_fail)
in that test, you will end up getting:
<class 'unittest.mock.MagicMock'>
This will certainly cause the hasattr
test to pass. If you run the above test again, except change your patch to be: @patch('some_module.Foo', autospec=True)
, it will fail as it should.
Now, to write a successful test for this and still use autospec=True, you simply create the attribute in your mock testing as needed. Remember, the reason this is needed, is because autospec cannot know about the attributes created dynamically, i.e. in the __init__
when you create an instance.
So, the autospec way to do this, would be:
class TestAutoSpec(unittest.TestCase):
@patch('some_module.Foo', autospec=True)
def test_autospec(self, mock_foo_class):
mock_foo_obj = mock_foo_class.return_value
# create the attribute you need from mocked Foo
mock_foo_obj.x = "potato"
bar_obj = some_module.Bar()
self.assertEqual(bar_obj.c.x, 'potato')
self.assertFalse(hasattr(bar_obj.c, 'poof'))
Now, your test will successfully pass at validating your x
attribute, while also validating that you don't have some bogus attribute that does not exist in your real Foo
class.
Here is also another explanation by Martijn Pieters, that does not necessarily directly answer your question, but gives a very good example and explanation of using autospec that can help further your understanding:
https://stackoverflow.com/a/31710001/1832539
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With