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()
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.
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.
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.
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
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())
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With