Mock.configure_mock fix for Python
Posted on Sat 07 May 2016 in Code • Tagged with Python, mock, patching • Leave a comment
Python’s mocking library is rather uncomplicated. Most of what it does is creating mock objects: veritable sponges that absorb every interaction with any code that we pass them to.
This simplicity is also surfaced in the API, especially in the main part of it — the mock.Mock
constructor:
some_mock = mock.Mock(url='http://example.com')
assert some_mock.url == 'http://example.com'
Any arguments that we pass there become attributes on the resulting mock object. This is really useful when
patching, because it allows us to completely specify
the replacement object within a @mock.patch
decorator:
@patch.object(requests, 'get', new=Mock(return_value=Mock(status_code=400)))
def test_404(self):
self.assertRaises(NotFoundError):
do_stuff()
You have to keep in mind, however, that the mock.Mock
class also has some
constructor arguments of its own.
For this reason, there exists some potential for name collision: some of the Mock
‘s own arguments may have the same
names as the attributes we’d like to set on the mock object:
some_mock = mock.Mock(name="John Doe") # doesn't set the `name` attribute
assert some_mock.name == "John Doe" # blows up!
Here, the name
argument is inherent to the Mock
class. Its constructor will interpret it in a special way,
and so it won’t set a name
attribute on the resulting mock. Other possible culprits include the spec
and wraps
parameters, both of which have relatively common names that we may want to use as object attributes1.
Collision avoidance
It’s trivial to fix the issue, of course:
some_mock = mock.Mock()
some_mock.name = "John Doe"
assert sock_mock.name == "John Doe"
but this approach has a downside. Creating and configuring a mock is no longer a single expression, which means we cannot use it with patchers as easily as before:
@patch.object(foo, 'bar')
def test_something(self, mock_bar):
mock_bar.name = "John Doe"
# (...rest of the test...)
We can either configure the mock after patching, like above, or perhaps introduce some utility functions to be called
inside the @patch
decorator.
The almost-there method
In any case, this is somewhat disappointing. And it is even more so when we discover that there is
a method called configure_mock
which looks like it was designed to solve this very issue. Its arguments are always interpreted as attributes
of the mock: it has no “special” or “reserved” names. Indeed, this method is what allows us to actually
write the mock setup as a single expression:
some_mock.configure_mock(name="John Doe")
Problem is, this expression returns None
.
Yes, configure_mock
returns nothing.
Or in other words, it doesn’t return anything.
In fact, it has no return
statement whatsoever.
Most importantly, it doesn’t have the return self
line that’d enable us to write this:
some_mock = mock.Mock().configure_mock(name="John Doe")
Well, that is quite a let-down.
Fixing it
But hey, this is Python! Shortcomings like that don’t necessarily mean we have to fork whole libraries.
Let’s just add the missing return
, shall we?
from mock import Mock as _Mock
class Mock(_Mock):
def configure_mock(self, **kwargs):
super(Mock, self).configure_mock(**kwargs)
return self # <-- there!
Whew, that was quick!
…Alright, that’s actually the whole fix, but it’s close. To complete it, we need to apply the same treatment to
three more Mock
classes: MagicMock
,
NonCallableMock
,
and NonCallableMagicMock
.
A complete solution can be seen in this gist.
-
Collision may also occur with
mock.patch
constructs. The most likely offender there is probably thenew
parameter. ↩