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