julienc
julienc

Reputation: 20325

mocking a parent class

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

Answers (2)

zvone
zvone

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

Daniel Roseman
Daniel Roseman

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

Related Questions