Turn Turn - 3 months ago 15
Python Question

Patching a parent class

I'm having trouble getting a parent class mocked with

mock.patch
.

Here's a test case:

In
parent.py
:

import mock

class Parent():
def __init__(self):
print("Original recipe")


In
child.py
:

from parent import Parent

class Child(Parent):
def foo(self):
print('Parent is {}'.format(Parent))


In
test.py
:

import mock

from child import Child

c = Child() # expect 'Original recipe'
c.foo()

with mock.patch('child.Parent'):
c = Child() # expect silence
c.foo()


When I run
test.py
I expect to get:

Original recipe
Parent is <class 'parent.Parent'>
Parent is <MagicMock name='Parent' id='4325705712'>


but instead I get:

Original recipe
Parent is <class 'parent.Parent'>
Original recipe
Parent is <MagicMock name='Parent' id='4325705712'>


So the patch is happening (from the "Parent is" statement) but not for the class inheritance. How can I fix that?

Answer

You are not patching the Parent class, you are patching the child module, changing its Parent attribute to a mock. This does not change Child at all, because it still uses the old Parent class as base class.

In Python 3, you can instead patch Child.__bases__ to change the base class at runtime. This come with its weirdnesses, of course.

Additional details

Python has no "variables", only names bound to specific objects in memory. Changing those names bindings (e.g. patching the scope they are contained it with mock.patch or setattr) has absolutely no effect on previous uses of these bindings.

This means that although you do patch the Parent attribute of the child module, replacing it by a Mock, as the module has already loaded, the class is already defined with the old target of the Parent attribute, which is, the original Parent class.

Other ways to test Child instances

As if Parent as external

with mock.patch('child.Child.method_that_calls_method_on_parent'):
    ...

If you want to isolate and mock method on Parent when testing instances of Child, you could make the calls to Parent sit in dedicated methods (and then patch these methods), as you'd do test external classes.

Patching methods on Parent

If you know in advance which methods of Parent you'll need to patch, you can just imply patch the methods on Parent.

with mock.patch('parent.Parent.method'):
    ...

This will mutate the value of the Parent attribute (which is the same for the child and parent modules, as child imports Parent from parent), instead of modifying which objects the Parent attribute points to in a particular module as you were doing before.

Making Parent behave like a Mock

with mock.patch('parent.Parent.__getattribute__'):
    ...

This is the most close to the intent of your original code. It relies on changing the way Python gets attributes from the Parent class, effectively patching all of its possible attributes.

The disadvantage with this is that you'd get a mock even for non-existing attributes, but that was the case with your original approach as well. This can be overcome by replacing __getattribute__ with a wrapper that returns a mock only for found attributes :

def _getmock(self, name):
    value = object.__getattribute__(self, name)
    return Mock(value)
_original = getattr(parent.Parent, '__getattribute__')
setattr(parent.Parent, '__getattribute__', _getmock)
try:
    ...
finally:
    setattr(parent.Parent, '__getattribute__', _original)

(Your test suite probably provides a way to temporarily patch _getmock as parent.Parent.__getattribute__, as mock.patch does, which would make this simpler.)

This can be further customized to specify the type and parameters of the mock created depending on the attribute name (name in _getmock) or value (value in _getmock), or making it so that the same mock is returned for when the same attribute name is accessed multiple times.

Comments