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

mock 0.8 beta 1: easier asserts for multiple and chained calls

emoticon:music I've released mock 0.8 beta 1. 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.

Note

This release introduces a backwards incompatibility with .call_args and .call_args_list. .call_args, and all the entries in .call_args_list, are "three-tuples" of (name, args, kwargs) instead of "two-tuples" of (args, kwargs).

This means that indexing into .call_args will now fetch the wrong entry.

I will fix this in a new beta release.

This is a beta release, so mock 0.8 is now "feature complete". The only changes will be bugfixes or any revisions to the new features based on feedback from you. The beta 1 release contains all the new features from the 0.8 alpha 2 release, plus a few new ones.

The most important change in beta 1 is a new, and greatly improved, way of making assertions about multiple or chained calls to a mock. The new features in beta 1 are [1]:

  • Addition of mock_calls list for all calls (including magic methods and chained calls)
  • Extension of call object to support chained calls and callargs for better comparisons with or without names. call object has a call_list method for chained calls
  • Mock call lists (call_args_list, method_calls & mock_calls) are now custom list objects that allow membership tests for "sub lists" and have an assert_has_calls method for unordered call checks
  • patch.TEST_PREFIX for controlling how patchers recognise test methods when used to decorate a class

Please test the beta!

The current way of making assertions about multiple and chained calls is a bit clumsy. Mock have two lists that store how they are called, and how child mocks (methods) have been called. These are call_args_lists and method_calls. For chained calls you had to manually extract the return_value mocks and make assertions on them individually.

mock 0.8 includes two new features for making this simpler. The first is a new attribute, mock_calls that records all calls - to the mock object, its methods, magic methods and return value mocks. The second is a call object for simpler construction of expected calls:

>>> from mock import MagicMock, call
>>> mock = MagicMock()
>>> mock(1, 2, 3)
<mock.MagicMock object at 0x57c5b0>
>>> mock.first(a=3)
<mock.MagicMock object at 0x58e750>
>>> mock.second()
<mock.MagicMock object at 0x59adf0>
>>> int(mock)
1
>>> expected = [call(1, 2, 3), call.first(a=3), call.second(), call.__int__()]
>>> mock.mock_calls == expected
True

A chained call is really a series of calls, so mock_calls will have each individual call in it:

>>> mock = MagicMock()
>>> mock.foo(1).bar(spam='eggs').baz.foo_again(2)
<mock.MagicMock object at 0x5cf9d0>
>>> mock.mock_calls
[('foo', (1,), {}), ('foo().bar', (), {'spam': 'eggs'}),
('foo().bar().baz.foo_again', (2,), {})]

You can either just make an assertion about the last call, or call call_list() on the call object to generate the list for you:

>>> expected = call.foo(1).bar(spam='eggs').baz.foo_again(2)
>>> mock.mock_calls == expected.call_list()
True

Instead of being 'dumb lists' the attributes call_args_list, method_calls and mock_calls are now special call list objects (a subclass of list). They allow you to do a membership test (using in) for a list of calls, where you want to check that a sequence of calls is in the list but don't care if there are additional calls before or after it. Where you want to check that certain calls were made, but don't care about the order, you can use the assert_has_calls method:

>>> mock = MagicMock(return_value=None)
>>> mock(1, 2)
>>> mock(3, 4)
>>> mock(5, 6)
>>> mock(7, 8)
>>> [call(3, 4), call(5, 6)] in mock.mock_calls
True
>>> some_calls = [call(7, 8), call(3, 4), call(1, 2)]
>>> mock.mock_calls.assert_has_calls(some_calls)

When you decorate a class with any of the patchers (patch, patch.dict, patch.object and now patch.multiple) it decorates any method whose name begins with test. There is a little known feature of the unittest.TestLoader that you can change the testMethodPrefix attribute to use a different prefix for it to recognise test methods. If you use anything other than test then you can now change patch.TEST_PREFIX to match.

In 0.8 beta 1 is a bug fix to patch.multiple. patch.multiple can now create mock objects for you (multiple of them if you want) by providing arg=DEFAULT. Where patch.multiple is creating mocks for you it passes them into a decorated function by keyword (because keyword arguments to patch.multiple are unordered), when used as a context manager it returns a dictionary keyed by name:

>>> from mock import patch, DEFAULT
>>> import os
>>> with patch.multiple(os.path, isfile=DEFAULT, isdir=DEFAULT) as mocks:
...     assert os.path.isfile is mocks['isfile']
...     assert os.path.isdir is mocks['isdir']
...

The failure messages for assert_called_with and assert_called_once_with improved with this release. A minor feature, but nice change:

>>> m = MagicMock()
>>> m.something(1, 2, 3)
<mock.MagicMock object at 0x56fe50>
>>> m.something.assert_called_with(1, 2, 5)
Traceback (most recent call last):
  ...
AssertionError: Expected call: something(1, 2, 5)
Actual call: something(1, 2, 3)

The biggest thing to do before the 0.8 final release is to update the docs for the new features in 0.8. The full list of changes since 0.7.2 is quite long:

  • Addition of mock_calls list for all calls (including magic methods and chained calls)
  • Mock call lists (call_args_list, method_calls & mock_calls) are now custom list objects that allow membership tests for "sub lists" and have an assert_has_calls method for unordered call checks, plus a nicer representation if you str or print them
  • patch and patch.object now create a MagicMock instead of a Mock by default
  • The patchers (patch, patch.object and patch.dict), plus Mock and MagicMock, take arbitrary keyword arguments for configuration
  • New mock method configure_mock for setting attributes and return values / side effects on the mock and its attributes
  • Implemented auto-speccing (recursive, lazy speccing of mocks with mocked signatures for functions/methods), as the autospec argument to patch
  • Added the create_autospec function for manually creating 'auto-specced' mocks
  • 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
  • Protocol methods on MagicMock are magic mocks, and are created lazily on first lookup. This means the result of calling a protocol method is a MagicMock instead of a Mock as it was previously
  • Added ANY for ignoring arguments in assert_called_with calls
  • Addition of call helper object
  • In Python 2.6 or more recent, dir on a mock will report all the dynamically created attributes (or the full list of attributes if there is a spec) as well as all the mock methods and attributes.
  • Module level FILTER_DIR added to control whether dir(mock) filters private attributes. True by default. Note that vars(Mock()) can still be used to get all instance attributes and dir(type(Mock()) will still return all the other attributes (irrespective of FILTER_DIR)
  • patch.TEST_PREFIX for controlling how patchers recognise test methods when used to decorate a class
  • Support for using Java exceptions as a side_effect on Jython
  • Improved failure messages for assert_called_with and assert_called_once_with
  • Added the Mock API (assert_called_with etc) to functions created by mocksignature
  • Tuples as well as lists can be used to specify allowed methods for spec & spec_set arguments
  • 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: 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
  • Removal of deprecated patch_object
  • callargs changed to always be a three-tuple of (name, args, kwargs)
  • Private attributes _name, _methods, '_children', _wraps and _parent (etc) renamed to reduce likelihood of clash with user attributes.
  • Added license file to the distribution

In other mock news, the name clash in Fedora has finally been resolved and mock will soon be packaged as python-mock.

[1]As always, the full set of changes can be seen in the changelog in the repo.

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

Posted by Fuzzyman on 2011-07-25 11:02:01 | |

Categories: , Tags: , ,


Hosted by Webfaction

Counter...