Creating custom matchers

The wide assortment of predefined matchers should be sufficient for a vast majority of your use cases.

But when they’re not, don’t worry. callee enables you to create your own, custom matchers quickly and succinctly. Those new matchers will be as capable as the standard ones, too, meaning you can use them in logical expressions, or with collection matchers such as List.

Here you can learn about all the possible ways of creating matchers with custom logic.

Predicates

The simplest technique is based on (re)using a predicate – that is, a function that returns a boolean result (True or False). This is handy when you already have a piece of code that recognizes objects you want to match.

Suppose you have this function:

def is_even(x):
    return x % 2 == 0

In order to turn it into an ad-hoc matcher, all need to do is wrap it in a Matching object:

mock_compute_half.assert_called_with(Matching(is_even))

Matching (also aliased as ArgThat) accepts any callable that takes a single argument – the object to match – and interprets its result as a boolean value.

As you may expect, returning True (or any Python “truthy” object) means that given argument matches the criteria. Otherwise, the match is considered unsuccessful. (If the function raises an exception, this is also interpreted as a failed match).

Since it’s valid to pass any Python callable to Matching/ArgThat, you can do basically anything there:

Matching(lambda x: x % 2 == 0)  # like above
ArgThat(is_prime)  # defined elsewhere
Matching(bool)  # matches any "truthy" value

For clearer code, however, you should strive to keep the predicates short and simple. Rather than writing a complicated lambda expression, for example, try to break it down and combine Matching/ArgThat with the built-in matchers.

If that proves difficult, it’s probably time to consider a custom matcher class instead.

Matcher classes

Ad-hoc matchers created with Matching/ArgThat are handy for some quick checks, but they have certain limitations:

  • They cannot accept parameters that modify their behavior (unless you parametrize the callable itself, which is clever but somewhat tricky and therefore not recommended).
  • The error messages they produce are not very informative, which makes it harder to debug and fix tests that use them.

These constraints are outgrown quickly when you use the same ad-hoc matcher more than once or twice.

Subclassing Matcher

The canonical way of creating a custom matcher type is to inherit from the Matcher base class.

The only method you need to override there is match. It shall take a single argument – the value to test – and return a boolean result:

class Even(Matcher):
    def match(self, value):
        return value % 2 == 0

The new matcher is immediately usable in assertions:

mock_compute_half.assert_called_with(Even())

or in any other context you’d normally use a matcher in.

Parametrized matchers

Because matchers deriving from the Matcher class are normal Python objects, their construction can be parametrized to provide additional flexibility.

The easiest and most common way is simply to save the arguments of __init__ as attributes on the object, so that the match method can access them as needed:

class Divisible(Matcher):
    """Matches a value that has given divisor."""

    def __init__(self, by):
        self.divisor = by

    def match(self, value):
        return value % self.divisor == 0

Usage of such a matcher is rather straightforward:

mock_compute_half.assert_called_with(Divisible(by=2))

Overriding __repr__

Custom matchers written as classes have one more advantage over ad-hoc ones. It is possible to redefine their __repr__ method, allowing for more informative error messages on failed assertions.

As an example, it would be good if Divisible matcher the from previous section told us what number it expected for the argument to be divisible by. This is easy enough to add:

def __repr__(self):
    return "<divisible by %d>" % (self.divisor,)

and makes relevant AssertionErrors more readable:

>>> mock_compute_half(3)
>>> mock_compute_half.assert_called_with(Divisible(by=2))
...
AssertionError: Expected call: mock(<divisible by 2>)
Actual call: mock(3)

In general, all parametrized matchers should probably override __repr__ to show, at a glance, what parameters they were instantiated with.

Note

The convention to surround matcher representations in angle brackets (<...>) is followed by all built-in matchers in callee, because it makes it easier to tell them apart from literal values. Adopting it for your own matches is therefore recommended.

Best practices

Ad-hoc matchers (those created with Matching/ArgThat) are best used judiciously. Ideally, you would want to involve them only if:

  • you already have a predicate you can use, or you can define one easily as a lambda
  • your test is very short, so that it’s easy to debug when it breaks

As a rule of thumb, whenever you define a function solely to use it with Matching/ArgThat, you should strongly consider creating a Matcher subclass instead. There is almost no additional boilerplate involved, and the resulting matcher will be more reusable and easier to extend.

Plus, if the new matcher turns up to be useful in multiple tests or projects, it can be added to callee itself!