Friday, 28 May 2010

Mockity mock mock - some love for the mock module

It looks like Python mocking libraries are the new web frameworks - everyone wrote one. Let me show you my favourite mocking library so far - the mock module, written by Michael Foord. It's easy_installable, so you can get to play with it in a moment.

easy_install mock
What makes it different than other modules like that? Most mocking libraries follow the old record-replay pattern, which works roughly like this:
  1. Teach a mock what to expect.
  2. Stick it into your code and expect an explosion if something didn't go as planned.

The excercise-inspect pattern in mock module reverses that approach:
  1. Stick the Mock object into your code (Mock being the class provided by mock module).
  2. Make sure that things happened the way you intended.

The tricky part is: how the hell is Mock able to fit everywhere? Well, it lets you do anything with it: get and set all attributes, and even call it when needed. And it leaves other Mocks everywhere it goes (it pretty much works like cancer). Let's play with Mock a little bit:

>>> from mock import Mock
>>> m = Mock()
>>> = 1
>>> # all attributes are recorded
>>> bar_attribute = # if no attribute was set, a mock instance is returned
<mock.Mock object at 0xdcff0>
>>> == bar_attribute # an attribute that was queried once stays there
>>> ret_val = m() # we can even treat mock as a function
>>> m() == ret_val == m.return_value # with similar cacheing behaviour
>>> # this is true of all methods as well (they're Mocks to, right?)
<mock.Mock object at 0xe4070>
>>> # mock remembers everything that happened to it
>>> m.baz.return_value = "hello!" # if we really need some setup
>>> m.baz() # nothing prevents us from doing it
>>> m.who.was.this.demeter.anyway.return_value.and_its_attribute # so just remember
<mock.Mock object at 0xe42f0>
>>> _ == m.who.was.this.demeter.anyway().and_its_attribute # there are Mocks all the way down!

Pretty cool, huh? But wait, there's more! Why should we laborously prepare, and then revert all the mocking ourselves? Let the library stick it where we need. We just need to import patch decorator.

import sys
from mock import patch

def read_4_chars():

def test_something(mock_stdin): = "abcd"
    assert read_4_chars() == "abcd"
    assert == ((4,), {})

def test_something_2():
    """You can use it with *with* statement as well!"""
    with patch("sys.stdin") as mock_stdin: = "abcd"
    assert read_4_chars() == "abcd"
    assert == ((4,), {})

There is much more that mock module has to offer. I suggest reading mock's excellent documentation.


Tartley said...

Love the Mock module. Nice write-up, and nice work by Michael - and whoever was responsible for what I infer is its spiritual predeceessor, Resolver's Listener object.

Paul Hildebrandt said...
This comment has been removed by the author.
Paul Hildebrandt said...

Thanks for the Mock examples and your work on Mock.

Laurent said...

Great examples, much easier to understand than the ones on the official Mock site.