How to set “return_value” once for TestCase. Python. Django

情到浓时终转凉″ 提交于 2019-12-11 13:45:12

问题


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 the Mock object:

  • [...]

  • return_value: The value returned when the mock is called. By default this is a new Mock (created on first access). See the return_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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!