5

In the code snippet shown below I would like to test the function call order in the run() function, i.e, f_3 is called after f_2 that is called after f_1:

class TestMock:

    def f_1(self) -> None:
        pass

    def f_2(self) -> None:
        pass

    def f_3(self) -> None:
        pass

    def run(self) -> None:
        self.f_1()
        self.f_2()
        self.f_3()

Is there any way to do this using pytest-mock? I have tried to mock the f_1, f_2, and f_3 functions in my test file and use assert_has_calls with any_order=False, however, without success.

Thanks in advance for any help or hints!

Best, Alexey

MrBean Bremen
  • 14,916
  • 3
  • 26
  • 46
Alexey Abramov
  • 435
  • 1
  • 3
  • 16
  • Does this answer your question? [Checking call order across multiple mocks](https://stackoverflow.com/questions/22677280/checking-call-order-across-multiple-mocks) – MrBean Bremen Aug 29 '20 at 13:30

2 Answers2

3

You were on the right track there with any_order=False, you just need to know about the attach_mock feature:

import yourmodule

def test_something(mocker):
    mock = mocker.MagicMock()
    mock.attach_mock(mocker.patch("yourmodule.TestMock.f_1"), "f_1")
    mock.attach_mock(mocker.patch("yourmodule.TestMock.f_2"), "f_2")
    mock.attach_mock(mocker.patch("yourmodule.TestMock.f_3"), "f_3")
    yourinstance = yourmodule.TestMock()
    yourinstance.run()
    mock.assert_has_calls(
        [
            mocker.call.f_1(),
            mocker.call.f_2(),
            mocker.call.f_3(),
        ],
        any_order=False,
    )
wim
  • 338,267
  • 99
  • 616
  • 750
  • thanks for your comment, it helped me further! Please check my answer and the revised code below, I have the follow-up question. – Alexey Abramov Aug 31 '20 at 14:00
0

I have switched completely to unittest.mock and have the following working code now:

@mock.patch('.'.join([__name__, 'TestMock', 'f_3']))
@mock.patch('.'.join([__name__, 'TestMock', 'f_2']))
@mock.patch('.'.join([__name__, 'TestMock', 'f_1']))
def test_order_2(f_1: mock.NonCallableMock,
                 f_2: mock.NonCallableMock,
                 f_3: mock.NonCallableMock) -> None:
    manager = mock.Mock()
    manager.attach_mock(f_1, 'f_1')
    manager.attach_mock(f_2, 'f_2')
    manager.attach_mock(f_3, 'f_3')

    obj = TestMock()
    obj.run()

    manager.assert_has_calls([mock.call.f_1,
                              mock.call.f_2,
                              mock.call.f_3], any_order=False)

here comes the question: I don't really understand the meaning behind attribute names so, they are mandatory, but the only reasonable names to me are the function names themselves... I have modified the attribute names as follows:

@mock.patch('.'.join([__name__, 'TestMock', 'f_3']))
@mock.patch('.'.join([__name__, 'TestMock', 'f_2']))
@mock.patch('.'.join([__name__, 'TestMock', 'f_1']))
def test_order_1(f_1: mock.NonCallableMock,
                 f_2: mock.NonCallableMock,
                 f_3: mock.NonCallableMock) -> None:
    manager = mock.Mock()
    manager.attach_mock(f_1, f_1._extract_mock_name())
    manager.attach_mock(f_2, f_2._extract_mock_name())
    manager.attach_mock(f_3, f_3._extract_mock_name())

    obj = TestMock()
    obj.run()

    manager.assert_has_calls([mock.call.f_1,
                              mock.call.f_2,
                              mock.call.f_3], any_order=False)

would be great to hear your opinion on this!

by the way, why f_* mock objects are of the mock.NonCallableMoc type? I would expect they are replaced by dummy functions (Collable)...

Thanks in advance for the help again!

Best, Alexey

Alexey Abramov
  • 435
  • 1
  • 3
  • 16