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
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
@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!
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
parameters, both of which have relatively common names that we may want to use as object attributes1.
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
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
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:
Problem is, this expression returns
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.
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!
A complete solution can be seen in this gist.