Reputation: 11047
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()
Upvotes: 36
Views: 74215
Reputation: 2484
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())
Upvotes: 3
Reputation: 19339
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
Upvotes: 39