问题
Here is example test:
import a
import b
import c
import mock
from django.test import TestCase
@mock.patch.object(a, "method_a")
@mock.patch.object(b, "method_b")
@mock.patch.object(c, "method_c")
class SomeTestCase(TestCase):
def setUp(self):
# I want to set mock_method_a.return_value = 1 once here (or not here, but once)
pass
def test_one(self, mock_method_a, mock_method_b, mock_method_c):
mock_method_a.return_value = 1
mock_method_b.return_value = 2
pass # some test stuff
def test_two(self, mock_method_a, mock_method_b, mock_method_c):
mock_method_a.return_value = 1
mock_method_b.return_value = 2
pass # some test stuff
def test_three(self, mock_method_a, mock_method_b, mock_method_c):
mock_method_a.return_value = 1
mock_method_b.return_value = 2
pass # some test stuff
Queston:
How I can avoid of duplicate code for setting "return_value" in each test in TestCase?
I expect something in "setUp" method or something similar.
Is it possible?
PS: mock version mock==1.3.0, django version Django==1.8.4
回答1:
You can set the return_value
right there in the @mock.patch.object()
decorators:
@mock.patch.object(c, "method_c", return_value=3)
@mock.patch.object(b, "method_b", return_value=2)
@mock.patch.object(a, "method_a", return_value=1)
class SomeTestCase(TestCase):
def test_one(self, mock_method_a, mock_method_b, mock_method_c):
# do test stuff, return values have been set
def test_two(self, mock_method_a, mock_method_b, mock_method_c):
# do test stuff, return values have been set
def test_three(self, mock_method_a, mock_method_b, mock_method_c):
# do test stuff, return values have been set
(Note: when decorating with @mock.patch
the decorators are applied from the bottom on up, so for mock_method_a
to be passed in as the first argument you need to put the decorator closest to the class definition).
The return_value
keyword argument to mock.patch.object()
is passed to the MagicMock()
constructor. See the mock.patch.object() documentation:
Like
patch()
,patch.object()
takes arbitrary keyword arguments for configuring the mock object it creates.
and the mock.Mock documentation:
Mock
takes several optional arguments that specify the behaviour of theMock
object:
[...]
return_value
: The value returned when the mock is called. By default this is a newMock
(created on first access). See thereturn_value
attribute.
If you also want to avoid setting the mocks outside of your test case or don't like the additional arguments to each test function, then you you can also can create patchers in the setUp
method, which then are removed again when the test ends by registering a callback via the unittest.TestCase.addCleanup() method.
The patchers are applied for each test, by calling the patcher.start() methods, which returns the new mock object:
class SomeTestCase(TestCase):
def setUp(self):
patcher_method_a = mock.patch.object(a, "method_a")
self.mock_method_a = patcher_method_a.start()
self.mock_method_a.return_value = 1
patcher_method_b = mock.patch.object(b, "method_b")
self.mock_method_b = patcher_method_b.start()
self.mock_method_b.return_value = 2
patcher_method_c = mock.patch.object(c, "method_c")
self.mock_method_c = patcher_method_c.start()
self.mock_method_c.return_value = 3
# when the test is done, stop **all** patchers
self.addCleanup(mock.patch.stopall)
def test_one(self):
# use self.mock_method_a, etc.
def test_two(self, mock_method_a, mock_method_b, mock_method_c):
# use self.mock_method_a, etc.
def test_three(self, mock_method_a, mock_method_b, mock_method_c):
# use self.mock_method_a, etc.
Note that the mock.patch.stopall()
method will stop all mock patchers that have started. You can also pass the .stop
attributes of each of the patchers:
self.addCleanup(patcher_method_a.stop)
self.addCleanup(patcher_method_b.stop)
self.addCleanup(patcher_method_c.stop)
If you have to create a lot of such setups, you could create a helper function that'll take care of the repeated parts:
def setup_object_patch(testcase, object, target, return_value, attrname=None):
patcher = mock.patch.object(object, target)
mock = patcher.start()
mock.return_value = return_value
setattr(testcase, attrname or f'mock_{target}', mock)
testcase.addCleanup(patcher.stop)
and perhaps use this in a loop over a mapping:
def setUp(self):
mocks = {
# attribute name on test -> object, target, return_value
'mock_method_a': (a, 'method_a', 1),
'mock_method_b': (b, 'method_b', 2),
'mock_method_c': (c, 'method_c', 3),
}
for attrname, params in mocks.items():
setup_object_patch(*params, attrname=attrname)
The patcher.start()
approach in a TestCase.setUp()
method makes it easier to use inheritance, where a base testcase case is used as the basis for several test cases that all use the same shared mocking setup.
来源:https://stackoverflow.com/questions/51710660/how-to-set-return-value-once-for-testcase-python-django