Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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() 
like image 467
bavaza Avatar asked May 25 '12 08:05

bavaza


People also ask

What is mock patch in Python?

patch() unittest. mock provides a powerful mechanism for mocking objects, called patch() , which looks up an object in a given module and replaces that object with a Mock . Usually, you use patch() as a decorator or a context manager to provide a scope in which you will mock the target object.

Can we mock a class in Python?

mock is a library for testing in Python. It allows you to replace parts of your system under test with mock objects and make assertions about how they have been used. unittest. mock provides a core Mock class removing the need to create a host of stubs throughout your test suite.

What is the difference between mock and patch?

Patching vs Mocking: Patching a function is adjusting it's functionality. In the context of unit testing we patch a dependency away; so we replace the dependency. Mocking is imitating. Usually we patch a function to use a mock we control instead of a dependency we don't control.


2 Answers

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 
like image 69
srgerg Avatar answered Sep 29 '22 12:09

srgerg


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()) 
like image 34
Peter K Avatar answered Sep 29 '22 12:09

Peter K