Reputation: 20325
I have a Parent
class that is inherited by a lot of ChildX
classes.
class Parent(object): # the class that should be mocked
def __init__(self):
assert False # should never happen, because we're supposed to use the mock instead
class Child1(Parent):
def method_1(self):
return 3
class MockParent(object): # this one should replace Parent
def __init__(self):
assert True
I have a test suite to run with pytest, and I want to make sure that, during these tests, everytime a ChildX
is instanciated, the instance would call MockParent
's methods instead of the Parent
's ones (I simplified a lot the example above, the __init__
method is not the only one concerned).
For the moment, all I've managed to do was to patch every ChildX
class one by one:
class FixturePatcher(object):
def __init__(self, klass):
self.klass = klass
self.patcher = None
def __enter__(self):
self.patcher = mock.patch.object(self.klass, '__bases__', (MockParent,))
return self.patcher.__enter__()
def __exit__(self, *_, **__):
self.patcher.is_local = True
@pytest.fixture()
def mock_parent_on_child1():
with FixturePatcher(Child1):
return Child1()
def test_mock_child1(mock_parent_on_child1):
assert mock_parent_on_child1.method_1() == 3
But I have many ChildX
classes and sometimes ChildY
is instanciated and used inside a method of ChildZ
, so I can't patch them all.
I've tried many things to replace Parent
by MockParent
, but none of them worked. Here are some of my failed attempts:
@mock.patch('src.parent.Parent', MockParent)
def test_mock_parent():
assert Child1().method_1() == 3 # Parent's __init__ method is still called
@mock.patch('src.parent.Parent.__getattribute__', lambda self, name: MockParent.__getattribute__(self, name))
def test_mock_parent():
assert Child1().method_1() == 3 # same results here
def test_mock_parent(monkeypatch):
monkeypatch.setattr('src.parent.Parent', MockParent)
assert Child1().method_1() == 3 # and still the same here
Is this even possible? I'm using python2.7 and the up-to-date versions of pytest
and mock
.
Upvotes: 2
Views: 10435
Reputation: 19352
I can't think of a way to replace Parent
with MockParent
everywhere where it is used, but you could monkeypatch Parent
's methods instead, if that is what you are after:
class Parent(object):
def my_method(self):
print 'my method'
class Child(Parent):
def run(self):
self.my_method()
child = Child()
child.run() # prints: my method
### Apply the monkeypatch: ###
def my_method(self):
print 'something else'
Parent.my_method = my_method
child.run() # prints: something else
You could also monkeypatch every method of Parent
with a method from MockParent
, in the same way. That would be almost the same as if you changed the parent class of everything inherited from Parent
.
EDIT:
In fact, you could probably also do exactly what you asked for if you searched for all existing subclasses of Parent
, patched them, and monkeypatched the definition if Parent
in its module so that future classes are updated as well:
for child_class in Parent.__subclasses__():
mock.patch.object(child_class, '__bases__', (MockParent,))
parents_module.Parent = MockParent
I think this would still miss some special situations, so monkeypatching each method is probably better.
Upvotes: 6
Reputation: 599610
No, it's not possible. The definitions of Child1 and Parent - including the inheritance of one from the other - are executed as soon as the module they are in is imported. There is no opportunity to get in before that and change the inheritance hierarchy.
The best you could do would be to move the definitions to some kind of factory function, which could dynamically define the parent of Child1 depending on what it was called with.
Upvotes: 3