catch2/docs/matchers.md
Martin Hořeňovský 2cc0c71856
Add a matcher that checks exception's message
Only works for exceptions that publicly derive from `std::exception`
and the matching is done exactly, including case and whitespace.

Closes #1649
Closes #1728

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
# Your branch is up-to-date with 'origin/master'.
#
# Changes to be committed:
#	modified:   ../docs/matchers.md
#	modified:   ../include/internal/catch_capture_matchers.h
#	modified:   ../projects/CMakeLists.txt
#	modified:   ../projects/SelfTest/Baselines/compact.sw.approved.txt
#	modified:   ../projects/SelfTest/Baselines/console.std.approved.txt
#	modified:   ../projects/SelfTest/Baselines/console.sw.approved.txt
#	modified:   ../projects/SelfTest/Baselines/junit.sw.approved.txt
#	modified:   ../projects/SelfTest/Baselines/xml.sw.approved.txt
#	modified:   ../projects/SelfTest/UsageTests/Matchers.tests.cpp
#
# Untracked files:
#	./
#	../clang-full/
#	../clang-test/
#	../clang10-build/
#	../coverage-build/
#	../gcc-build/
#	../gcc-full/
#	../include/internal/catch_matchers_exception.cpp
#	../include/internal/catch_matchers_exception.hpp
#	../misc-build/
#	../msvc-sln/
#	../notes.txt
#	../test-install/
#
2019-10-13 20:37:07 +02:00

6.3 KiB

Matchers

Matchers are an alternative way to do assertions which are easily extensible and composable. This makes them well suited to use with more complex types (such as collections) or your own custom types. Matchers were first popularised by the Hamcrest family of frameworks.

In use

Matchers are introduced with the REQUIRE_THAT or CHECK_THAT macros, which take two arguments. The first argument is the thing (object or value) under test. The second part is a match expression, which consists of either a single matcher or one or more matchers combined using &&, || or ! operators.

For example, to assert that a string ends with a certain substring:

using Catch::Matchers::EndsWith; // or Catch::EndsWith
std::string str = getStringFromSomewhere();
REQUIRE_THAT( str, EndsWith( "as a service" ) ); 

The matcher objects can take multiple arguments, allowing more fine tuning. The built-in string matchers, for example, take a second argument specifying whether the comparison is case sensitive or not:

REQUIRE_THAT( str, EndsWith( "as a service", Catch::CaseSensitive::No ) ); 

And matchers can be combined:

REQUIRE_THAT( str, 
    EndsWith( "as a service" ) || 
    (StartsWith( "Big data" ) && !Contains( "web scale" ) ) ); 

Built in matchers

Catch currently provides some matchers, they are in the Catch::Matchers and Catch namespaces.

String matchers

The string matchers are StartsWith, EndsWith, Contains, Equals and Matches. The first four match a literal (sub)string against a result, while Matches takes and matches an ECMAScript regex. Do note that Matches matches the string as a whole, meaning that "abc" will not match against "abcd", but "abc.*" will.

Each of the provided std::string matchers also takes an optional second argument, that decides case sensitivity (by-default, they are case sensitive).

Vector matchers

Catch2 currently provides 5 built-in matchers that work on std::vector. These are

  • Contains which checks whether a specified vector is present in the result
  • VectorContains which checks whether a specified element is present in the result
  • Equals which checks whether the result is exactly equal (order matters) to a specific vector
  • UnorderedEquals which checks whether the result is equal to a specific vector under a permutation
  • Approx which checks whether the result is "approx-equal" (order matters, but comparison is done via Approx) to a specific vector

Approx matcher was introduced in Catch 2.7.2.

Floating point matchers

The floating point matchers are WithinULP and WithinAbs. WithinAbs accepts floating point numbers that are within a certain margin of target. WithinULP performs an ULP-based comparison of two floating point numbers and accepts them if they are less than certain number of ULPs apart.

Do note that ULP-based checks only make sense when both compared numbers are of the same type and WithinULP will use type of its argument as the target type. This means that WithinULP(1.f, 1) will expect to compare floats, but WithinULP(1., 1) will expect to compare doubles.

Generic matchers

Catch also aims to provide a set of generic matchers. Currently this set contains only a matcher that takes arbitrary callable predicate and applies it onto the provided object.

Because of type inference limitations, the argument type of the predicate has to be provided explicitly. Example:

REQUIRE_THAT("Hello olleH",
             Predicate<std::string>(
                 [] (std::string const& str) -> bool { return str.front() == str.back(); },
                 "First and last character should be equal")
);

The second argument is an optional description of the predicate, and is used only during reporting of the result.

Exception matchers

Catch2 also provides an exception matcher that can be used to verify that an exception's message exactly matches desired string. The matcher is ExceptionMessageMatcher, and we also provide a helper function Message.

The matched exception must publicly derive from std::exception and the message matching is done exactly, including case.

ExceptionMessageMatcher was introduced in Catch X.Y.Z

Example use:

REQUIRE_THROWS_MATCHES(throwsDerivedException(),  DerivedException,  Message("DerivedException::what"));

Custom matchers

It's easy to provide your own matchers to extend Catch or just to work with your own types.

You need to provide two things:

  1. A matcher class, derived from Catch::MatcherBase<T> - where T is the type being tested. The constructor takes and stores any arguments needed (e.g. something to compare against) and you must override two methods: match() and describe().
  2. A simple builder function. This is what is actually called from the test code and allows overloading.

Here's an example for asserting that an integer falls within a given range (note that it is all inline for the sake of keeping the example short):

// The matcher class
class IntRange : public Catch::MatcherBase<int> {
    int m_begin, m_end;
public:
    IntRange( int begin, int end ) : m_begin( begin ), m_end( end ) {}

    // Performs the test for this matcher
    bool match( int const& i ) const override {
        return i >= m_begin && i <= m_end;
    }

    // Produces a string describing what this matcher does. It should
    // include any provided data (the begin/ end in this case) and
    // be written as if it were stating a fact (in the output it will be
    // preceded by the value under test).
    virtual std::string describe() const override {
        std::ostringstream ss;
        ss << "is between " << m_begin << " and " << m_end;
        return ss.str();
    }
};

// The builder function
inline IntRange IsBetween( int begin, int end ) {
    return IntRange( begin, end );
}

// ...

// Usage
TEST_CASE("Integers are within a range")
{
    CHECK_THAT( 3, IsBetween( 1, 10 ) );
    CHECK_THAT( 100, IsBetween( 1, 10 ) );
}

Running this test gives the following in the console:

/**/TestFile.cpp:123: FAILED:
  CHECK_THAT( 100, IsBetween( 1, 10 ) )
with expansion:
  100 is between 1 and 10

Home