Python mock: mocking base class for inheritance

|▌冷眼眸甩不掉的悲伤 提交于 2019-12-31 22:15:44

问题


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.


回答1:


This should work for you.

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='...'>

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



回答2:


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.



来源:https://stackoverflow.com/questions/19817511/python-mock-mocking-base-class-for-inheritance

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!