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

Release: Mock 0.4.0, Library for Mocking and Testing

emoticon:Lithium Mock 0.4.0 has just been released, the first release in about ten months (but worth the wait).

Mock is a simple library for testing: specifically for mocking, stubbing and patching.

This new release brings in some cool new features, but some are backwards incompatible so read the release notes if you are updating. Thanks to all those who emailed me with suggestions and feature requests - most of them got in one way or another...

What is Mock?

Mock makes simple mocking trivially easy. It also provides decorators to help patch module and class level attributes within the scope of a test. In this release the patch decorator got even easier to use. You can now call it with one argument - a string in the form 'package_name.module_name.ClassName'. This is the fully qualified name of the object you wish to patch.

patch patches the class in the specified module with a Mock object, which is passed in to the decorated function. (As well as patch creating mocks for you, you can also explicitly pass in an alternative object to patch with.)

What's New?

The are now eggs (for Python 2.4-2.6) up on the Mock Cheeseshop Page. This means that if you have setuptools you should be able to install mock with:

easy_install mock

The full changelog is:

  • Default return value is now a new mock rather than None

  • 'return_value' added as a keyword argument to the constructor

  • New method 'assert_called_with'

  • Added 'side_effect' attribute / keyword argument called when mock is called

  • patch decorator split into two decorators:

    • patch_object which takes an object and an attribute name to patch (plus optionally a value to patch with which defaults to a mock object)
    • patch which takes a string specifying a target to patch; in the form 'package.module.Class.attribute'. (plus optionally a value to patch with which defaults to a mock object)
  • Can now patch objects with None

  • Change to patch for nose compatibility with error reporting in wrapped functions

  • Reset no longer clears children / return value etc - it just resets call count and call args. It also calls reset on all children (and the return value if it is a mock).

Thanks to Konrad Delong, Kevin Dangoor and others for patches and suggestions.

Examples

As an example, suppose we want to test the following code:

from somewhere import ClassName

class ProductionClass(object):
    def create_instance(self):
        self.instance = ClassName()

We can test that the create_instance method of ProductionClass instantiates ClassName by patching it at the module level. This is awkward to do manually, but trivially easy with patch.

@patch('production_module.ClassName')
def testSomething(self, MockClass):
    mock_instance = MockClass.return_value

    real_object = ProductionClass()
    real_object.create_instance()
    self.assertEquals(real_object.instance, mock_instance,
                      "create_instance didn't create and store ClassName instance")

Mock objects create attributes on demand (new mocks), and for the same attributes always return the same mock. You can limit the attributes available using the spec and method keyword arguments. They provide several attributes for making assertions about how they have been used. One of my favourites is the new convenience method assert_called_with:

>>> from mock import Mock
>>> mock = Mock()
>>> mock.some_method('hello', goodbye=False)
<mock.Mock object at 0x6f050>
>>>
>>> mock.some_method.assert_called_with('goodbye', hello=False)
Traceback (most recent call last):
  ...
AssertionError: Expected: (('goodbye',), {'hello': False})
Called with: (('hello',), {'goodbye': False})

If you need your mock to raise an exception when called (to test the exception handling of an API), or want to return members of a sequence for repeated calls, you can use the side_effect keyword argument / attribute to do this:

>>> from mock import Mock
>>> def effect():
...     raise Exception('Boom!')
...
>>> mock = Mock(side_effect=effect)
>>> mock()
Traceback (most recent call last):
  ...
Exception: Boom!

>>> results = [1, 2, 3]
>>> def effect():
...     mock.return_value = results.pop()
...
>>> mock.side_effect = effect
>>> mock(), mock(), mock()
(3, 2, 1)

The Future

The major thing that Mock doesn't do, is provide any ways to customize magic methods like __iter__ or the sequence / mapping protocols. At work (where we use Mock extensively but have effectively forked it) we have implemented these in an ad-hoc nature as we've needed them - a strategy I'm not very happy with as a general solution.

One possibility would simply to implement them all, with a clean mechanism for specifying return values. This would pollute the default Mock class with a lot of extraneous methods, and potentially cause problems with duck-typing where you rely on objects not having implemented certain methods.

I have in mind a class factory solution where you specify which protocol (magic) methods you need and get back a custom mock that implements only these. It will have to be for the next release, and preferably I will implement it when I actually need it so that I can get a feel for whether it is the right solution.

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

Posted by Fuzzyman on 2008-10-13 11:36:03 | |

Categories: , Tags: , , , ,


Hosted by Webfaction

Counter...