The Problem
Using mock.patch
with autospec=True
to patch a class is not preserving attributes of instances of that class.
No, autospeccing cannot mock out attributes set in the __init__
method of the original class (or in any other method). It can only mock out static attributes, everything that can be found on the class.
Otherwise, the mock would have to create an instance of the class you tried to replace with a mock in the first place, which is not a good idea (think classes that create a lot of real resources when instantiated).
The recursive nature of an auto-specced mock is then limited to those static attributes; if foo
is a class attribute, accessing Foo().foo
will return an auto-specced mock for that attribute. If you have a class Spam
whose eggs
attribute is an object of type Ham
, then the mock of Spam.eggs
will be an auto-specced mock of the Ham
class.
The documentation you read explicitly covers this:
A more serious problem is that it is common for instance attributes to be created in the
__init__
method and not to exist on the class at all.autospec
can’t know about any dynamically created attributes and restricts the api to visible attributes.
You should just set the missing attributes yourself:
@patch('foo.Foo', autospec=TestFoo)
def test_patched(self, mock_Foo):
mock_Foo.return_value.foo = 'foo'
Bar().bar()
or create a subclass of your Foo
class for testing purposes that adds the attribute as a class attribute:
class TestFoo(foo.Foo):
foo = 'foo' # class attribute
@patch('foo.Foo', autospec=TestFoo)
def test_patched(self, mock_Foo):
Bar().bar()