Python mock: mocking base class for inheritance

前端 未结 2 1586
星月不相逢
星月不相逢 2020-12-30 04:38

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 nic

相关标签:
2条回答
  • 2020-12-30 05:20

    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
    
    0 讨论(0)
  • 2020-12-30 05:21

    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.

    0 讨论(0)
提交回复
热议问题