Python Programming, news on the Voidspace Python Projects and all things techie.

Mocking patterns: chained calls, partial mocking and open as context manager

emoticon:clock In recent weeks I've received two requests for help with mocking (and testing) chained calls. The mock library makes this easy, but I'm guessing that it isn't immediately obvious. At the same time I had a use case today for partially mocking out a class, and a user on Testing in Python asked about mocking the builtin open when it is used as a context manager. This blog entry shows these three patterns.

Mocking chained calls

Mocking chained calls is actually straightforward with mock once you understand the return_value attribute. When a mock is called for the first time, or you fetch its return_value before it has been called, a new Mock is created. (It can't be created whenever a mock is instantiated as otherwise the return value Mock would also need its return value creating, and so on ad infinitum.)

This means that you can see how the object returned from a call to a mocked object has been used by interrogating the return_value mock:

>>> from mock import Mock
>>> mock = Mock()
>>> mock().foo(a=2, b=3)
>>> mock.return_value.foo.assert_called_with(a=2, b=3)

From here it is a simple step to configure and then make assertions about chained calls. Of course another alternative is writing your code in a more testable way in the first place... Wink

So, suppose we have some code that looks a little bit like this:

class Something(object):
    def __init__(self):
        self.backend = BackendProvider()

    def method(self):
        response = self.backend.get_endpoint('foobar').create_call('spam', 'eggs').start_call()
        # more code

Assuming that BackendProvider is already well tested, how do we test method()? Specifically, we want to test that the code section # more code uses the response object in the correct way.

As this chain of calls is made from an instance attribute we can monkey patch the backend attribute on a Something instance. In this particular case we are only interested in the return value from the final call to start_call so we don't have much configuration to do. Let's assume the object it returns is 'file-like', so we'll ensure that our response object uses the builtin file as its spec.

To do this we create a mock instance as our mock backend and create a mock response object for it. To set the response as the return value for that final start_call we could do this: mock_backend.get_endpoint.return_value.create_call.return_value.start_call.return_value = mock_response.

Here's how we might do it in a slightly nicer way:

def test_something_method():
    something = Something()

    mock_response = Mock(spec=file)
    mock_backend = Mock()

    get_endpoint = mock_backend.get_endpoint
    create_call = get_endpoint.return_value.create_call
    start_call = create_call.return_value.start_call
    start_call.return_value = mock_response

    # monkeypatch the backend attribute
    something.backend = mock_backend

    something.method()

    get_endpoint.assert_called_with('foobar')
    create_call.assert_called_with('spam', 'eggs')
    start_call.assert_called_with()

    # make assertions on mock_response about how it is used

Keeping references to the intermediate methods makes our assertions easier, and also makes the code less ugly.

Partial mocking

Ok, so the title here is a bit weird. In some tests I wanted to mock out a call to datetime.date.today() to return a known date [1], but I didn't want to prevent the code under test from creating new date objects. Unfortunately datetime.date is written in C, and so I couldn't just monkey-patch out the static date.today method.

I found a simple way of doing this that involved effectively wrapping the date class with a mock, but passing through calls to the constructor to the real class (and returning real instances).

The patch decorator is used here to mock out the date class in the module under test. The side_effect attribute on the mock date class is then set to a lambda function that returns a real date. When the mock date class is called a real date will be constructed and returned by side_effect.

>>> from datetime import date
>>> with patch('mymodule.date') as mock_date:
...     mock_date.today.return_value = date(2010, 10, 8)
...     mock_date.side_effect = lambda *args, **kw: date(*args, **kw)
...     ...
...

Note that we don't patch datetime.date globally, we patch date in the module that uses it. This is a point that seems to confuse people when they start using patch. Obviously a point that needs making more strongly in the intro docs.

When date.today() is called a known date is returned, but calls to the date(...) constructor still return normal dates. Without this you can find yourself having to calculate an expected result using exactly the same algorithm as the code under test, which is a classic testing anti-pattern.

Calls to the date constructor are recorded in the mock_date attributes (call_count and friends) which may also be useful for your tests.

Mocking open

The soon-to-be-out-of-beta-unless-you-find-any-bugs version 0.7.0 of mock supports mocking magic methods. One of the things you can now do is use Mock to mock out objects used as context managers in a with statement.

The specific question was, what is the best way to mock out the use of the builtin open function in code that looks like this:

with open('/some/path') as f:
    f.write('something')

The issue is that even if you mock out the call to open it is the returned object that is used as a context manager (and has __enter__ and __exit__ called). So there are two parts two this question and one of them comes back to the ever wonderful return_value.

So first the topic of creating a mock object that can be called, with the return value able to act as a context manager. The easiest way of doing this is to use the new MagicMock class, which is preconfigured to be able to act as a context manger. As an added bonus we'll use the spec argument to ensure that the mocked object can only be used in the same ways a real file could be used (attempting to access a method or attribute not on the file will raise an AttributeError):

>>> from mock import Mock, MagicMock
>>> mock_open = Mock()
>>> mock_open.return_value = MagicMock(spec=file)

In terms of configuring our mock this is all that needs to be done. In fact it could be constructed with a one liner: mock_open = Mock(return_value=MagicMock(spec=file)).

So what is the best way of patching the builtin open function? One way would be to globally patch __builtin__.open. So long as you are sure that none of the other code being called also accesses open this is perfectly reasonable. It does make some people nervous however. By default we can't patch the open name in the module where it is used, because open doesn't exist as an attribute in that namespace. patch refuses to patch attributes that don't exist because that is a great way of having tests that pass but code that is horribly broken (your code can access attributes that only exist during your tests!). patch will however create (and then remove again) non-existent attributes if you tell it that you are really sure you know what you're doing.

By passing create=True into patch we can just patch the open function in the module under test instead of patching it globally:

>>> with patch('module.open', create=True) as mock_open:
...     mock_open.return_value = MagicMock(spec=file)
...     with open('/some/path', 'w') as f:
...         f.write('something')
...
<mock.Mock object at 0x...>
>>> file_handle = mock_open.return_value.__enter__.return_value
>>> file_handle.write.assert_called_with('something')

Job done.


[1]And yes, this was to fix a test that would only fail if run on the 1st of the month...

Like this post? Digg it or Del.icio.us it.

Posted by Fuzzyman on 2010-10-08 15:54:26 | |

Categories: , Tags: , ,


Hosted by Webfaction

Counter...