简体   繁体   中英

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?

Mock actually provides something like this builtin. Mocks frequently have a parent mock ... eg

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...)

An initial attempt at solving this is with a specialised Mock subclass which registers the calls in a provided sequence object, which can be whatever shared sequence you like.

from copy import deepcopy
import mock

class CallRegisterMock(mock.MagicMock):
    """ A mock object that registers each call. """

    def __init__(self, call_register, *args, **kwargs):
        super(CallRegisterMock, self).__init__(*args, **kwargs)

        self.call_register = call_register

    def __call__(self, *args, **kwargs):
        args = deepcopy(args)
        kwargs = deepcopy(kwargs)
        call = mock.call(*args, **kwargs)
        qualified_call = (self, call)
        self.call_register.append(qualified_call)
        super(CallRegisterMock, self).__call__(*args, **kwargs)

This has drawbacks:

  • It may be re-inventing one or more wheels. (Please add a better answer if you think so.)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM