Checking call order across multiple mocks Checking call order across multiple mocks python python

Checking call order across multiple mocks


Define a Mock manager and attach mocks to it via attach_mock(). Then check for the mock_calls:

@patch('module.a')@patch('module.b')@patch('module.c')def test_main_routine(c, b, a):    manager = Mock()    manager.attach_mock(a, 'a')    manager.attach_mock(b, 'b')    manager.attach_mock(c, 'c')    module.main_routine()    expected_calls = [call.a('a'), call.b('b'), call.c('c')]    assert manager.mock_calls == expected_calls

Just to test that it works, change the order of function calls in the main_routine() function add see that it throws AssertionError.

See more examples at Tracking order of calls and less verbose call assertions

Hope that helps.


I needed this answer today, but the example code in the question is really hard to read because the call args are the same as the names of the mocks on the manager and in the scope of the test. Here's the official documentation on this concept, and below is a clearer example for non-robots. All the modules I'm patching are made-up for the sake of the example:

@patch('module.file_reader')@patch('module.json_parser')@patch('module.calculator')def test_main_routine(mock_calculator, mock_json_parser, mock_file_reader):    manager = Mock()    # First argument is the mock to attach to the manager.    # Second is the name for the field on the manager that holds the mock.    manager.attach_mock(mock_file_reader, 'the_mock_file_reader')    manager.attach_mock(mock_json_parser, 'the_mock_json_parser')    manager.attach_mock(mock_calculator, 'the_mock_calculator')        module.main_routine()    expected_calls = [        call.the_mock_file_reader('some file'),        call.the_mock_json_parser('some json'),        call.the_mock_calculator(1, 2)    ]    assert manager.mock_calls == expected_calls

Note that you have to use attach_mock in this case because your mocks were created by patch. Mocks with names, including those created by patch, must be attached via attach_mock for this code to work. You don't have to use attach_mock if you make your own Mock objects without names:

def test_main_routine(mock_calculator, mock_json_parser, mock_file_reader):    manager = Mock()    mock_file_reader = Mock()    mock_json_parser = Mock()    mock_calculator = Mock()    manager.the_mock_file_reader = mock_file_reader    manager.the_mock_json_parser = mock_json_parser    manager.the_mock_calculator = mock_calculator        module.main_routine()    expected_calls = [        call.the_mock_file_reader('some file'),        call.the_mock_json_parser('some json'),        call.the_mock_calculator(1, 2)    ]    assert manager.mock_calls == expected_calls

If you want a clear assertion failed message when the order or expected calls are missing, use the following assert line instead.

self.assertListEqual(manager.mock_calls, [    call.the_mock_file_reader('some file'),    call.the_mock_json_parser('some json'),    call.the_mock_calculator(1, 2)])


A cleaner solution would be to wrap your functions into a class, then mock the class in the test. This will eliminate the need to do any patching (always a plus).

# module.pyclass Wrapper:    def a(self, *args):        pass    def b(self, *args):        pass    def c(self, *args):        pass    def main_routine(self):        a_args = ('arg for a',)        b_args = ('arg for b',)        c_args = ('arg for c',)        self.a(*a_args)        self.b(*b_args)        self.c(*c_args)

In the test file, you create a mock wrapper class, then insert the mock wrapper in as the argument self when calling Wrapper.main_method (notice that this does not instantiate the class).

# module_test.pyfrom unittest.mock import MagicMock, callfrom module import Wrapperdef test_main_routine():    mock_wrapper = MagicMock()    Wrapper.main_routine(mock_wrapper)    expected_calls = [call.a('arg for a'),                      call.b('arg for b'),                      call.c('arg for c')]    mock_wrapper.assert_has_calls(expected_calls)

Benefits:

  • No patching needed
  • In the test, you only need to type the name of the method being called once (instead of 2-3 times)
  • Uses assert_has_calls instead of comparing the mock_calls attribute to a list of calls.
  • Can be made into a general check_for_calls function (see below)
# module_better_test.pyfrom unittest.mock import MagicMock, callfrom module import Wrapperdef test_main_routine():    expected_calls = [call.a('arg for a'),                      call.b('arg for b'),                      call.c('arg for c')]    check_for_calls('main_routine', expected_calls)def check_for_calls(method, expected_calls):    mock_wrapper = MagicMock()    getattr(Wrapper, method)(mock_wrapper)    mock_wrapper.assert_has_calls(expected_calls)