Python mock: mocking base class for inheritance

Question:

I am testing a class that inherits from another one very complex, with DB connection methods and a mess of dependences. I would like to mock its base class so that I can nicely play with the method defined in the subclass, but in the moment I inherit from a mocked class, the object itself turns a mock and loses all its methods.

How can I mock a superclass?

More or less the situation can be summed up in this:

import mock

ClassMock = mock.MagicMock()

class RealClass(ClassMock):

    def lol(self):
        print 'lol'

real = RealClass()
real.lol()  # Does not print lol, but returns another mock

print real # prints <MagicMock id='...'>

This is a simplified case. What is actually happening is that RealClass extends AnotherClass, but I managed to intercept the AnotherClass and replace it with a mock.

Asked By: bgusach

||

Answers:

This should work for you.

import mock

ClassMock = mock.MagicMock # <-- Note the removed brackets '()'

class RealClass(ClassMock):

    def lol(self):
        print 'lol'

real = RealClass()
real.lol()  # Does not print lol, but returns another mock

print real # prints <MagicMock id='...'>

You should’nt pass an instance of the class as you did. mock.MagicMock is a class, so you pass it directly.

In [2]: inspect.isclass(mock.MagicMock)
Out[2]: True
Answered By: user1301404

This is something I’ve been struggling with for a long time, but I think I’ve finally found a solution.

As you already noticed, if you try to replace the base class with a Mock, the class you’re attempting to test simply becomes the mock, which defeats your ability to test it. The solution is to mock only the base class’s methods rather than the entire base class itself, but that’s easier said than done: it can be quite error prone to mock every single method one by one on a test by test basis.

What I’ve done instead is created a class that scans another class, and assigns to itself Mock()s that match the methods on the other class. You can then use this class in place of the real base class in your testing.

Here is the fake class:

class Fake(object):
    """Create Mock()ed methods that match another class's methods."""

    @classmethod
    def imitate(cls, *others):
        for other in others:
            for name in other.__dict__:
                try:
                    setattr(cls, name, Mock())
                except (TypeError, AttributeError):
                    pass
        return cls

So for example you might have some code like this (apologies this is a little bit contrived, just assume that BaseClass and SecondClass are doing non-trivial work and contain many methods and aren’t even necessarily defined by you at all):

class BaseClass:
    def do_expensive_calculation(self):
        return 5 + 5

class SecondClass:
    def do_second_calculation(self):
        return 2 * 2

class MyClass(BaseClass, SecondClass):
    def my_calculation(self):
        return self.do_expensive_calculation(), self.do_second_calculation()

You would then be able to write some tests like this:

class MyTestCase(unittest.TestCase):
    def setUp(self):
        MyClass.__bases__ = (Fake.imitate(BaseClass, SecondBase),)

    def test_my_methods_only(self):
        myclass = MyClass()
        self.assertEqual(myclass.my_calculation(), (
            myclass.do_expensive_calculation.return_value, 
            myclass.do_second_calculation.return_value,
        ))
        myclass.do_expensive_calculation.assert_called_once_with()
        myclass.do_second_calculation.assert_called_once_with()

So the methods that exist on the base classes remain available as mocks you can interact with, but your class does not itself become a mock.

And I’ve been careful to ensure that this works in both python2 and python3.

Answered By: robru

I was facing a similar problem and was able to do this via @patch.object. See examples for patch decorators in the official python doc.

class MyTest(unittest.TestCase):
    @patch.object(SomeClass, 'inherited_method')
    def test_something(self, mock_method):
        SomeClass.static_method()
        mock_method.assert_called_with()
Answered By: Akash

Just exemplifying @Akash‘s answer, which was the one that in fact solved my inheritance mock challenge:

 @patch.object(SomeClassInheritingAnother, "inherited_method")
    def test_should_test_something(self, mocked_inherited_method, mocker, caplog):
       #Mocking an HTTP result status code
       type(mocked_inherited_method.return_value).status_code = mocker.PropertyMock(return_value=200)

       #Calling the inherited method, that should end up using the mocked method
       SomeClassInheritingAnother.inherited_method()

       #Considering that the request result is being logged as 'Request result: {response.status_code}'
       assert "Request result: 200" in caplog.text 
Answered By: LucianoBAF