Writing Your Own Matchers§

mettle is designed to make it easy to write your own matchers to complement the built-in suite of matchers. This makes it easier to test the state of objects with complex properties. Once created, user-defined matchers can be composed as normal with built-in matchers as you'd expect.

Helper functions§

make_matcher(function, desc)§

The easiest way to create your own matcher is with the make_matcher function. This takes two parameters: first, a function object that accepts a value of any type, and returns a bool (with true naturally meaning a successful match); and second, a string describing the matcher.

make_matcher returns a basic_matcher<void, F>, where F is the type of the function, but it's easier to just deduce the return type. For instance, here's a simple matcher that returns true when the actual value is 4:

auto match_four() {
  return make_matcher([](const auto &value) -> bool {
    return value == 4;
  }, "== 4");
}

make_matcher(capture, function, prefix)§

You can also capture a value to use with your matcher; while you certainly can capture the value via a lambda, passing the variable directly to make_matcher allows it to be printed automatically when desc() is called. In this overload, function works as above, except that it takes a second argument for the captured object. The final argument, prefix, is a string that will be prepended to the printed form of capture.

This overload of make_matcher returns a basic_matcher<T, F>, where T is the type of the capture and F is the type of the function. Again, it's easier to just deduce the return type. Here's an example of a matcher that returns true when two numbers are off by one:

template<typename T>
auto off_by_one(T &&expected) {
  return make_matcher(
    std::forward<T>(expected),
    [](const auto &actual, const auto &expected) -> bool {
      auto x = std::minmax<T>(actual, expected);
      return x.second - x.first == 1;
    }, "off by 1 from "
  );
}

ensure_matcher(thing)§

Sometimes, a matcher should be able to accept other matchers as an argument. However, we also typically want to be able to pass in arbitrary objects as a shorthand to implicitly call the equal_to matcher. In this case, we'd use ensure_matcher.

ensure_matcher wraps an object with the equal_to matcher, or returns the passed-in matcher if the object is already a matcher. As an example, let's create a matcher that returns true if exactly one of its arguments is true:

template<typename T>
auto either(T &&a, T &&b) {
  auto a_matcher = ensure_matcher(std::forward<T>(a));
  auto b_matcher = ensure_matcher(std::forward<T>(b));

  return make_matcher([a_matcher, b_matcher](const auto &value) -> bool {
    return a_matcher(value) ^ b_matcher(value);
  }, a_matcher.desc() + " xor " + b_matcher.desc());
}

Starting from scratch§

For particularly complex matchers, make_matcher may not provide much value. In these cases, you can instead build your own matcher from scratch. First, and most importantly, all matchers must inherit from matcher_tag. This removes any ambiguity between actual matchers and types that just have a similar interface.

As the previous section hints at, a matcher must also have a const overloaded operator () that takes a value of any type and returns a bool, and a const desc function that returns a string description of the matcher.

A matcher made from scratch isn't much more complex than one made using the helper functions above; most of the complexity will come from the behaviors you define. Here's a simple example that never returns true:

struct nothing : matcher_tag {
  template<typename T>
  bool operator ()(const U &value) const {
    return false;
  }

  std::string desc() const {
    return "nothing";
  }
};

Further examples§

Naturally, many more examples of matchers can be found in mettle's own source code. Feel free to consult these to get some ideas for how to implement your own matchers!