How do you mock patch a python class and get a new Mock object for each instantiation?

46,502

Solution 1

Here's a quick'n'dirty example to get you going:

import mock
import unittest

class ClassToPatch():
   def __init__(self, *args):
       pass

   def some_func(self):
       return id(self)

class UUT():
    def __init__(self, *args):
        resource_1 = ClassToPatch()
        resource_2 = ClassToPatch()
        self.test_property = (resource_1.some_func(), resource_2.some_func())

class TestCase1(unittest.TestCase):
    @mock.patch('__main__.ClassToPatch', autospec = True)
    def test_1(self, mock1):
        ctpMocks = [mock.Mock(), mock.Mock()]
        ctpMocks[0].some_func.return_value = "funky"
        ctpMocks[1].some_func.return_value = "monkey"
        mock1.side_effect = ctpMocks

        u = UUT()
        self.assertEqual(u.test_property, ("funky", "monkey"))

if __name__ == '__main__':
    unittest.main()

I've added test_property to UUT so that the unit test does something useful. Now, without the mock test_property should be a tuple containing the ids of the two ClassToPatch instances. But with the mock it should be the tuple: ("funky", "monkey").

I've used the side_effect property of the mock object so that a different instance of ClassToPatch is returned on each call in the UUT initialiser.

Hope this helps.

Edit: Oh, by the way, when I run the unit test I get:

.
----------------------------------------------------------------------
Ran 1 test in 0.004s

OK

Solution 2

Here is another version which is more generic to handle any number of instances created:

class TestUUT:
    def test_init(self, mocker):
        class MockedClassToPatchMeta(type):
            static_instance = mocker.MagicMock(spec=ClassToPatch)

            def __getattr__(cls, key):
                return MockedClassToPatchMeta.static_instance.__getattr__(key)

        class MockedClassToPatch(metaclass=MockedClassToPatchMeta):
            original_cls = ClassToPatch
            instances = []

            def __new__(cls, *args, **kwargs):
                MockedClassToPatch.instances.append(
                    mocker.MagicMock(spec=MockedClassToPatch.original_cls))
                MockedClassToPatch.instances[-1].__class__ = MockedClassToPatch
                return MockedClassToPatch.instances[-1]

        mocker.patch(__name__ + '.ClassToPatch', new=MockedClassToPatch)

        UUT()

        # since your original code created two instances
        assert 2 == len(MockedClassToPatch.instances)

If you need more thorough validation for each instance you can access MockedClassToPatch.instances[0] or MockedClassToPatch.instances[1].

I've also created a helper library to generate the meta class boilerplate for me. To generate the needed code for your example I wrote:

print(PytestMocker(mocked=ClassToPatch, name=__name__).mock_classes().mock_classes_static().generate())
Share:
46,502
bavaza
Author by

bavaza

Updated on November 25, 2020

Comments

  • bavaza
    bavaza over 3 years

    OK,
    I know this is mentioned in the manual, and probably has to do with side_effect and/or return_value, but a simple, direct example will help me immensely.

    I have:

    class ClassToPatch():
       def __init__(self, *args):
           _do_some_init_stuff()
    
       def some_func():
           _do_stuff()
    
    
    class UUT():
        def __init__(self, *args)
           resource_1 = ClassToPatch()
           resource_2 = ClassToPatch()
    

    Now, I want to unit test the UUT class, and mock the ClassToPatch. Knowing the UUT class will instantiate exactly two ClassToPatch objects, I want the Mock framework to return a new Mock object for each instantiation, so I can later assert calls on each separately.

    How do I achieve this using the @patch decorator in a test case? Namely, how to fix the following code sample?

    class TestCase1(unittest.TestCase):
    
        @patch('classToPatch.ClassToPatch',autospec=True)
        def test_1(self,mock1,mock2):
            _assert_stuff()