Search code examples
pythonunit-testingpython-mock

Assert a sequence of calls to different mock objects


How can I use the Python mock library to assert a specific sequence of calls to different mock objects?

For example, I want to assert:

  • A call of foo(spam, eggs); then
  • A call of bar(beans, ham); then
  • A call of foo(sausage).

I can patch each of foo and bar, and the resulting mock objects each allow me to make assertions about calls to that mock. But I need to access an overall sequence of calls to make assertions about that sequence.

Yes, ideally I would need only to inspect the resulting state and make assertion about it after the fact. But that's not feasible with some systems, and the only workable description of correct state is “these calls were made in this particular sequence”.

What capabilities of the mock library can I use for accessing a sequence of calls to different objects, and asserting the calls were as expected in the right sequence?


Solution

  • Mock actually provides something like this builtin. Mocks frequently have a parent mock ... e.g.

    somemock.foo  # parent is somemock
    

    the parent isn't directly exposed in the mock API, But the calls on the children are registered on the parent.

    import mock
    m = mock.Mock()
    m.a('hello world')
    m.b('goodbye world')
    m.c('kittens!')
    m.a('Howdy')
    m.assert_has_calls([
      mock.call.a('hello world'),
      mock.call.b('goodbye world'),
      mock.call.c('kittens!'),
      mock.call.a('Howdy')
    ])  # passes silently
    m.assert_has_calls([
      mock.call.a('hello world'),
      mock.call.b('goodbye world'),
      mock.call.a('Howdy'),
      mock.call.c('kittens!')
    ]) # Error
    # Traceback (most recent call last):
    #   File "<stdin>", line 1, in <module>
    #   File "/usr/local/lib/python2.7/dist-packages/mock.py", line 863, in assert_has_calls
    #     'Actual: %r' % (calls, self.mock_calls)
    # AssertionError: Calls not found.
    # Expected: [call.a('hello world'), call.b('goodbye world'), call.a('Howdy'), call.c('kittens!')]
    # Actual: [call.a('hello world'),
    #  call.b('goodbye world'),
    #  call.c('kittens!'),
    #  call.a('Howdy')]
    

    But, "My mocks don't all come from the same parent" you might be saying. All is not yet lost! You can create a parent and attach them to it after the fact.

    parent_mock = mock.Mock()
    parent_mock.attach_mock(foomock, 'foo')
    parent_mock.attach_mock(barmock, 'bar')
    

    and now you can do the same sort of asserting we did above (so long as you don't need to preserve the original mocks' parents ... then I'm not sure what to tell you...)