17

Coming from a static programming language background, I am wondering how to best do mocking in Python. I am accustomed to dependency injection. Within tests, mocks are created and passed to the System Under Test (SUT). However, looking at Mock and other mock frameworks for Python, it appears that the types/functions/etc. in the module are replaced on a test-by-test basis.

Particularly, with Mock, atop each unit test you say @patch('some.type.in.the.module.under.test') for each type/function/etc. you want to mock. For the lifetime of the test those things are mocked, then they are reverted. Unfortunately, across tests, the fixture is pretty close to the same and you end up repeating your @patches over and over again.

I want a way to share a collection of patches across unit tests. I also want carry through tweaks to the fixture in a composable way. I am okay using a context manager instead of a decorator.

Travis Parks
  • 8,435
  • 12
  • 52
  • 85
  • Huh, I always thought that SUT meant "Software Under Test", not "System". Learned something new! – voithos Jul 25 '12 at 19:59

4 Answers4

13

You can patch the test class which flows down to each method in that class. And then you could inherit from a super class and work with the the setUp and tearDown methods.

import unittest 

@patch('some.type.in.the.module.under.test')
class MySuperTestCase(unittest.TestCase):
    pass

class MyActualTestCase(MySuperTestCase):

    def test_method(self, mock_function)
        mock_function.return_value = False

This is less general than you might think though. Because you need to patch the object in the exact location where it is being used. You don't patch 'sys.stdout', you patch 'my_dir.my_module.sys.stdout'. So it would really only be of use when testing a particular module. But to test that particular model you could certainly only need one single patch decorator.

aychedee
  • 24,871
  • 8
  • 79
  • 83
  • I wasn't aware that `@patch` worked on classes. Does this mean each test method has to have a parameter for each patched object? – Travis Parks Jul 26 '12 at 12:17
  • Yup, and they'll all be positional. For situations where you don't care about them there is always *args – aychedee Jul 26 '12 at 13:57
  • This is still awesome to know. I didn't even see it when reading the docs. Still, this requires "test class per fixture" but it is moving in the right direction. – Travis Parks Jul 26 '12 at 15:03
  • 1
    The way I unittest I have a number of test classes that do different types of setup and then at least one, if not more, test classes that inherit from the various super classes per test module. Works for me. Patching modules is usually so specific for each test that I don't really feel a lot of duplication. – aychedee Jul 26 '12 at 16:20
5

I recently ran into a similar situation but more extreme. One of my top level modules had to mock out several repositories, providers, and logic libraries. This resulted in a number of unit tests that needed to @patch 7 components. I wanted to avoid a lot of duplicate test code so here was my solution which worked pretty well:

@mock.patch('module.blah1.method1')      # index: 6
@mock.patch('module.blah1.method2')      # index: 5
@mock.patch('module.blah2.method1')      # index: 4
@mock.patch('module.blah2.method2')      # index: 3
@mock.patch('module.blah2.method3')      # index: 2
@mock.patch('module.blah3.method1')      # index: 1
@mock.patch('module.blah4.method1')      # index: 0
class TestsForMyCode(unittest.TestCase):

    def test_first_test(self, *mocks):
        # Arrange

        # setup mocks for only the ones that need specific mocked behaviours

        # idx 2 patches module.blah2.method3
        mocks[2].return_value = 'some value'

        # Act
        target = sut()
        result = target.do_something()

        # Assert
        assert result is False

    def test_second_test(self, *mocks):
        # Arrange

        # setup mocks for only the ones that need specific mocked behaviours

        # idx 0 patches module.blah4.method1
        mocks[0].return_value = 'another value'

        # idx 4 patches module.blah2.method1
        mocks[4].return_value = 'another value'

        # Act
        target = sut()
        result = target.do_something_else()

        # Assert
        assert result is True

The @mocks on the class are applied to each test when run and pass all the patches into the *mocks parameter. The important thing to remember is the ordering - I place the index comments in my code to keep it straight in my head.

Hope this helps.

Nae
  • 14,209
  • 7
  • 52
  • 79
Lars Laakes
  • 51
  • 1
  • 1
1

I don't guarantee this is syntactically correct since I have no way to test it, but here goes:

COMMON_FUNCTIONS = ('some.type.in.the.module.under.test', 'and.others')
def common_patches(f):
    for item in COMMON_FUNCTIONS:
        f = patch(item)(f)

Now apply it with:

@common_patches
def something():
    pass # it will be decorated by all the patches in the function
Gabi Purcaru
  • 30,940
  • 9
  • 79
  • 95
  • Using COMMON_FUNCTIONS like this is a very one-and-done approach. I need to write unit tests dozens of times a day with shared fixture building logic. I don't think decorators are the solution. Although, I think a context-manager-based solution would be along the same lines. – Travis Parks Jul 26 '12 at 02:06
1

I would also recommend decorators, as you can avoid redundant patch. And not just that, using parameterized decorators, you can control custom fixtures for each decorator. Example:

def patch_example(custom_value=None):
    def _patch(test_func):
        @mock.patch('some.type.in.the.module.under.test')
        def _patch_it(mocked_function):
            mocked_function = custom_value
            return test_func(self)
        return wraps(test_func)(_patch_it)
    return _patch

class ExampleTestCase(object):

    @patch_example(custom_value='new_value')
    def test_method_1(self):
        # your test logic here, with mocked values already defined in decorator

    @patch_example(custom_value='new_value_2')
    def test_method_2(self):
        # your test logic here, with mocked values already defined in decorator
Jernej Jerin
  • 3,179
  • 9
  • 37
  • 53