Using matchers with
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.
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.
You can use all callee matchers any time you are asserting on calls received by
mock objects. They are applicable as arguments to any of the following methods:
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
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.
Specifically, given matchers
A | Bmatches objects that match
A & Bmatches objects that match both
~Amatches objects do not match
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.
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:
- General matchers
- String matchers
- Numeric matchers
- Collection matchers
- Operator matchers
If your needs can’t be met by it, there is always a possibility of defining your own matchers as well.