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

Mock subclasses and their attributes

emoticon:paper This blog entry is about creating subclasses of mock.Mock. mock is a library for testing in Python. It allows you to replace parts of your system under test with mock objects. The latest stable release is 0.7.2, which you can download from pypi.

There are various reasons why you might want to subclass Mock. One reason might be to add helper methods. Here's a silly example:

>>> from mock import MagicMock
>>> class MyMock(MagicMock):
...     def has_been_called(self):
...         return self.called
...
>>> mymock = MyMock(return_value=None)
>>> mymock.has_been_called()
False
>>> mymock()
>>> mymock.has_been_called()
True

The standard behaviour for Mock instances is that attributes and the return value are of the same type as the mock they are accessed on. This is so that Mock attributes are Mocks and MagicMock attributes are MagicMocks [1]. So if you're subclassing to add helper methods then they'll also be available on the attributes and return value mock of instances of your subclass.

>>> mymock.foo.has_been_called()
False
>>> mymock.foo()
<mock.MyMock object at 0x5747b0>
>>> mymock.foo.has_been_called()
True

Sometimes this is inconvenient. For example, one user is subclassing mock to created a Twisted adaptor. Having this applied to attributes too actually causes errors.

Mock (in all its flavours) uses a method called _get_child_mock to create these "sub-mocks" for attributes and return values. You can prevent your subclass being used for attributes by overriding this method. The signature is that it takes arbitrary keyword arguments (**kwargs) which are then passed onto the mock constructor:

>>> class Subclass(MagicMock):
...     def _get_child_mock(self, **kwargs):
...         return MagicMock(**kwargs)
...
>>> mymock = Subclass()
>>> mymock.foo
<MagicMock name='mock.foo' id='5696720'>
>>> assert isinstance(mymock, Subclass)
>>> assert not isinstance(mymock.foo, Subclass)
>>> assert not isinstance(mymock(), Subclass)

This works with mock 0.7 and 0.8 (and possibly earlier versions but they're no longer supported officially), and in 0.8 will be documented and tested so you can rely on it continuing to work.

[1]Mock 0.8 introduces a couple of new mock types that aren't callable. Their attributes are callable (otherwise non-callable mocks couldn't have methods), so they're an exception to this rule.

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

Posted by Fuzzyman on 2011-07-18 17:22:00 | |

Categories: , , Tags: ,


Mock 0.8 alpha 2: patch.multiple, new_callable and non-callable mocks

emoticon:music I've released mock 0.8 alpha 2. You can download it it or install it with:

pip install -U mock==dev

mock is a library for testing in Python. It allows you to replace parts of your system under test with mock objects. The latest stable release is 0.7.2, which you can download from pypi.

As this is an alpha release it isn't feature complete. There are still a lot of features I'm looking at. The alpha 2 release contains all the new features from the 0.8 alpha 1 release, plus the following major changes:

  • patch.multiple for doing multiple patches in a single call, using keyword arguments
  • New new_callable argument to patch and patch.object allowing you to pass in a class or callable object (instead of MagicMock) that will be called to replace the object being patched
  • Addition of NonCallableMock and NonCallableMagicMock, mocks without a __call__ method

There are also the following more minor changes:

  • Mocks created by patch have a MagicMock as the return_value where a class is being patched
  • create_autospec can create non-callable mocks for non-callable objects. return_value mocks of classes will be non-callable unless the class has a __call__ method
  • autospec creates a MagicMock without a spec for properties and slot descriptors, because we don't know the type of object they return
  • Removed the "inherit" argument from create_autospec
  • Calling stop on an unstarted patcher fails with a more meaningful error message
  • BUGFIX: an error creating a patch, with nested patch decorators, won't leave patches in place
  • BUGFIX: __truediv__ and __rtruediv__ not available as magic methods on mocks in Python 3
  • BUGFIX: assert_called_with / assert_called_once_with can be used with self as a keyword argument
  • BUGFIX: autospec for functions / methods with an argument named self that isn't the first argument no longer broken
  • BUGFIX: when patching a class with an explicit spec / spec_set (not a boolean) it applies "spec inheritance" to the return value of the created mock (the "instance")
  • BUGFIX: remove the __unittest marker causing traceback truncation

The most interesting new feature in 0.8 alpha 2 is the addition of patch.multiple. This allows you to make several patches to an object in a single call. The target object can either be passed in directly, or specified via a string to be imported. A typical usecase for this (in fact the usecase that inspired the feature) is doing multiple patches to django settings in one line:

with patch.multiple(settings, THING='thing', OTHER='other'):
    ...

with patch.multiple('django.conf.settings', THING='thing', OTHER='other'):
    ...

The new non-callable mocks are straightforward, and only occasionally useful. They're useful where code, like django templating, treats callable objects differently from non-callable ones.

callable_new exists partly to make it easier to use the different mock variants. For example to patch something with a non-callable mock:

@patch('thing.otherthing', new_callable=NonCallableMock)
def test_thing(self, mock_otherthing):
    ...

An alternative usecase is where you're re-using a decorated function or method and want to patch something with a new object each time. For example patching sys.stdout with a StringIO instance:

@patch('sys.stdout', new_callable=StringIO)
def helper_method(self, mock_stdout):
    ...

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

Posted by Fuzzyman on 2011-07-16 15:36:19 | |

Categories: , Tags: ,


Hosted by Webfaction

Counter...