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()
>>> m.foo = 1
>>> m.foo # all attributes are recorded
1
>>> bar_attribute = m.bar # if no attribute was set, a mock instance is returned
<mock.Mock object at 0xdcff0>
>>> m.bar == bar_attribute # an attribute that was queried once stays there
True
>>> ret_val = m() # we can even treat mock as a function
>>> m() == ret_val == m.return_value # with similar cacheing behaviour
True
>>> m.bar() # this is true of all methods as well (they're Mocks to, right?)
<mock.Mock object at 0xe4070>
>>> m.bar.called # mock remembers everything that happened to it
True
>>> m.baz.return_value = "hello!" # if we really need some setup
>>> m.baz() # nothing prevents us from doing it
'hello!'
>>> 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!
True

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():
    return sys.stdin.read(4)

@patch("sys.stdin")
def test_something(mock_stdin):
    mock_stdin.read.return_value = "abcd"
    assert read_4_chars() == "abcd"
    assert mock_stdin.read.called
    assert mock_stdin.read.call_args == ((4,), {})

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

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