Using matchers with mock
¶
Mocks – or more generally, test doubles – are used to provide the necessary dependencies (objects, functions, data, etc.) to the code under test. We often configure mocks to expose an interface that the code can rely on. We also expect it to make use of this interface in a well-defined, predictable way.
In Python, the configuration part mostly taken care of by the mock
library. But when it comes to asserting
that the expected mocks interactions had happened, callee can help quite a bit.
Example¶
Suppose you are testing the controller of a landing page for users that are signed in to your web application. The page should display a portion of the most recent items of interest – posts, notifications, or anything else specific to the service.
The test seems straightforward enough:
@mock.patch.object(database, 'fetch_recent_items')
def test_landing_page(self, mock_fetch_recent_items):
login_user(self.user)
self.http_client.get('/')
mock_fetch_recent_items.assert_called_with(count=8)
Unfortunately, the assert it contains turns out to be quite brittle. If you think about it, the number of items to display is very much a UX decision, and it likely changes pretty often as the UI is iterated upon. But with a test like that, you have to go back and modify the assertion whenever the value is adjusted in the production code.
Not good! The test shouldn’t really care what the exact count is. As long as it’s a positive integer, maybe except 1 or 2, the test should pass just fine.
Using argument matchers provided by callee, you can express this intent clearly and concisely:
from callee import GreaterThan, Integer
# ...
mock_fetch_recent_items.assert_called_with(
count=Integer() & GreaterThan(1))
Much better! Now you can tweak the layout of the page without further issue.
Matching basics¶
You can use all callee matchers any time you are asserting on calls received by Mock
, MagicMock
,
or other mock
objects. They are applicable as arguments to any of the following methods:
assert_called_with
assert_called_once_with
assert_any_call
assert_not_called
Moreover, the mock.call
helper can also accept matchers in place of call arguments. This enables you to also use
the assert_has_calls
method if you like:
some_mock.assert_has_calls([
call(0, String()),
call(1, String()),
call(2, String()),
])
Finally, you can still leverage matchers even when you’re working directly with the call_args_list
, method_calls
,
or mock_calls
attributes. The only reason you’d still want that, though, is verifying the exact calls a mock receives, in order:
assert some_mock.call_args_list == [
mock.call(String(), Integer()),
mock.call(String(), 42),
]
But most tests don’t need to be this rigid, so remember to use this technique sparingly.
Combining matchers¶
Individual matchers, such as String
or Float
,
can be combined to build more complex expressions. This is accomplished with Python’s “logical” operators:
|
, &
, and ~
.
Specifically, given matchers A
and B
:
A | B
matches objects that matchA
orB
A & B
matches objects that match bothA
andB
~A
matches objects do not matchA
Here’s a few examples:
some_mock.assert_called_with(Number() | InstanceOf(Foo))
some_mock.assert_called_with(String() & ShorterThan(9))
some_mock.assert_called_with(String() & ~Contains('forbidden'))
All matchers can be combined this way, including any custom ones that you write yourself.
Next steps¶
Now that you know how to use matchers and how to combine them into more complex expressions, you probably want to have a look at the wide array of existing matchers offerred by callee:
If your needs can’t be met by it, there is always a possibility of defining your own matchers as well.