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?
Inheritance concept is to inherit properties from one class to another but not vice versa. But since parent class reference variable points to sub class objects. So it is possible to access child class properties by parent class object if only the down casting is allowed or possible....
Parent class is the class being inherited from, also called base class. Child class is the class that inherits from another class, also called derived class.
side_effect: A function to be called whenever the Mock is called. See the side_effect attribute. Useful for raising exceptions or dynamically changing return values. The function is called with the same arguments as the mock, and unless it returns DEFAULT , the return value of this function is used as the return value.
How do we mock in Python? Mocking in Python is done by using patch to hijack an API function or object creation call. When patch intercepts a call, it returns a MagicMock object by default. By setting properties on the MagicMock object, you can mock the API call to return any value you want or raise an Exception .
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.
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.
Child
instancesParent
as externalwith 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.
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.
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.
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