Nested Matchers in GTest
Asked Answered
P

1

5

I would like to use some existing matchers in other matcher. I know about the MatcherInterface solution but I was wondering can I use matchers which were defined by MATCHER_P. If found this solution:

struct Foo
{
    double x;
    double y;
};

struct Bar
{
    Foo foo;
    int i;
};

MATCHER_P(EqFoo, foo, "")
{
    ::testing::Matcher<double> x_matcher = ::testing::DoubleNear(foo.x, 0.0001);
    if (!x_matcher.MatchAndExplain(arg.x, result_listener))
    {
        return false;
    }

    ::testing::Matcher<double> y_matcher = ::testing::DoubleNear(foo.y, 0.0001);
    if (!y_matcher.MatchAndExplain(arg.y, result_listener))
    {
        return false;
    }

    return true;
}

MATCHER_P(EqBar, bar, "")
{
    ::testing::Matcher<Foo> foo_matcher = EqFooMatcherP<Foo>(bar.foo);

    if (!foo_matcher.MatchAndExplain(arg.foo, result_listener))
    {
        return false;
    }

    if (bar.i != arg.i)
    {
        return false;
    }

    return true;
}

TEST_F(TestClass, BarTest)
{
    Bar bar_val{{10.12, 76.43}, 78};
    Bar bar_exp{{10.12, 99.99}, 78};

    EXPECT_THAT(bar_val, EqBar(bar_exp));
}

I am just wondering, is there any better and nicer solution to

  • use my own MATCHER_P matcher in another one
  • use an original GTest matcher in another one.
Pleading answered 26/2, 2018 at 12:8 Comment(2)
Is your test an example? Else you will not need a matcher.Sonjasonnet
Sure, this is a simplification of my original code.Festinate
W
10

The correct way is to use, as much as possible, the matchers from gtest/gmock. Only if there is no already provided matchers - use your own.

In your example - it is just as simple as this:

auto EqFoo(const Foo& expected)
{
   return ::testing::AllOf(
         ::testing::Field(&Foo::x, ::testing::DoubleNear(expected.x, 0.0001)),
         ::testing::Field(&Foo::y, ::testing::DoubleNear(expected.y, 0.0001))
     );
}

auto EqBar(const Bar& expected)
{
   return ::testing::AllOf(
         ::testing::Field(&Bar::foo, EqFoo(expected.foo)),
         ::testing::Field(&Bar::i, expected.i)
     );
}

More general approach is to use overloads:

auto MatchDouble(double expected)   
{
    return ::testing::DoubleNear(expected.x, 0.0001);
}
auto MatchFoo(::testing::Matcher<double> x, ::testing::Matcher<double> y)
{
   return ::testing::AllOf(
         ::testing::Field(&Foo::x, x),
         ::testing::Field(&Foo::y, y)
     );
}
auto MatchFoo(double x, double y)
{
   return MatchFoo(MatchDouble(x), MatchDouble(y));
}

auto MatchBar(::testing::Matcher<Foo> foo, ::testing::Matcher<int> i)
{
   return ::testing::AllOf(
         ::testing::Field(&Bar::foo, foo),
         ::testing::Field(&Bar::i, expected.i),
     );
}
auto MatchBar(const Bar& expected)
{
   return MatchBar(expected.foo, expected.i);
}

So your test:

TEST_F(TestClass, BarTest)
{
    Bar bar_val{{10.12, 76.43}, 78};
    Bar bar_exp{{10.12, 99.99}, 78};

    EXPECT_THAT(bar_val, MatchBar(bar_exp));

    // or - e.g. you can match only Bar::y if other things are irrelevant in your test
    EXPECT_THAT(bar_val, MatchBar(MatchFoo(_, MatchDouble(2.001)), _);
}

Anyway - using MATCHER_P should be rather rare case, my own observation is that this macro is really overused.

In case your project is pre-C++14 - use ::testing::Matcher<T> instead of auto as return type for all of these functions.

Weeny answered 1/3, 2018 at 11:36 Comment(5)
And if you're using mocks and passing the struct as a pointer, do forget to do Pointee(EqFoo(...)) or you will get some strange compiler errors.Whiteheaded
would there be a way to perform a type conversion between Field/Property and the value being matched? I have a scenario where I need to an extra API call to convert to std::string / C-style string to be able to call various string matcher gmock has (e.g. HasSubstr). OR a custom matcher that the conversion from myString to std::string so that i can use the HasSubstr matcher. Is that possible?Wriggle
@ArcinB you can create your own MyHasSubstr matcher by MATCHER_P macro - where in the body you can call HasSubstr. Or you can check gmock doc if they have any "converting matchers" - recently when I checked it was not anything like that there.Weeny
MATCHER_P is what I have implemented as well. @PiotrNycz. But there were a few answers, here and elsewhere, recommending capabilities of the default matchers and combining them. It is much nicer, not to need to point to a new matcher implementation. Basic need is having the result converted before getting matched. Does gmock provide no such capability?Wriggle
@ArcinB as I wrote - there is no converting-value matchers. You might write such matcher - like template <typename Functor> class ConvertingMatcher ...;} For some more detailed advices you will need to ask question about - as a question - not comment to another question.Weeny

© 2022 - 2024 — McMap. All rights reserved.