Python Programming, news on the Voidspace Python Projects and all things techie.
mock 0.8 release candidate 1 and handling mutable arguments
pip install -U mock==dev
The main improvement in 0.8 rc1 is performance improvements, particularly in constructing MagiMock. Instantiating MagicMock is substantially faster than it was in 0.7 already, however patch creates a MagicMock by default now (in 0.7 patch creates a Mock by default). Instantiating MagicMock in 0.8 is still slower than instantiating a Mock in 0.7, but the difference is much less than it used to be.
There are also a couple of bugfixes. The full changelog for 0.8 rc1 is:
- create_autospec on the return value of a mock class will use __call__ for the signature rather than __init__
- Performance improvement instantiating Mock and MagicMock
- Mocks used as magic methods have the same type as their parent instead of being hardcoded to MagicMock
The additional changes in 0.8 are explained in these blog entries:
- 0.8 beta 4 release
- 0.8 beta 3 release
- 0.8 beta 2 release
- 0.8 beta 1 release
- 0.8 alpha 2 release
- 0.8 alpha 1 release
Special thanks to Julian Berman for his help with diagnosing and improving performance in this release. I have started work on the doc changes and I'm hoping that the 0.8 final release won't be too far off.
Mock, and its now multifarious variants, are designed to "remember" arguments they are called with for making assertions about how they have been used. There are two ways it could do this: it could either keep references to the arguments or it could copy them.
Mock chooses to keep a reference to the arguments rather than copying them, mainly because this was easier to implement and met all my use cases at the time. As a consequence, if a mock is called with mutable arguments (like a set or a dictionary) that are changed (mutated) in between the call and the assertion then you have to make the assertion against the changed value rather than the value as it was at the call time. Here's an example of what I mean:
>>> from mock import MagicMock >>> arg = set() >>> m = MagicMock(return_value=None) >>> m(arg) >>> arg.add(1) >>> m.assert_called_with(set()) Traceback (most recent call last): ... AssertionError: Expected call: mock(set()) Actual call: mock(set())
Even though m was called with an empty set, an assertion with an empty set fails.
The alternative would be to copy all arguments for every call (using copy.deepcopy). Unfortunately deepcopy is slow and fragile, and although it would solve this problem with mutable arguments it would break assertions that rely on object identity.
>>> from copy import deepcopy >>> class CopyingMock(MagicMock): ... def __call__(self, *args, **kwargs): ... args = deepcopy(args) ... kwargs = deepcopy(kwargs) ... return super(CopyingMock, self).__call__(*args, **kwargs) ... >>> c = CopyingMock(return_value=None) >>> arg = set() >>> c(arg) >>> arg.add(1) >>> c.assert_called_with(set()) >>> c.assert_called_with(arg) Traceback (most recent call last): ... AssertionError: Expected call: mock(set()) Actual call: mock(set()) >>> c.foo <CopyingMock name='mock.foo' id='...'>
When you subclass Mock or MagicMock all dynamically created attributes, and the return_value will use your subclass automatically. That means all children of a CopyingMock will also have the type CopyingMock.
This goes nicely with the new-in-0.8 new_callable argument to patch that allows you to specify the mock class to use:
with patch('some.object', new_callable=CopyMock) as mock_thing: # do things...
This work is licensed under a Creative Commons Attribution-Share Alike 2.0 License.