1

I have a unit test that validates whether certain member values (valueA, valueB) equate to 0 or not. And the object being tested i.e Object is a template and what I am looking to accomplish is to:

wrap the validation part in a function so instead of invoking EXPECT_NE/EXPECT_EQ for each member value, I just invoke a function that takes care of the validation

This is the original snippet:

template<typename T>
struct Object
{
    struct Values
    {
        int valueA;
        int valueB;
    };
    Values values = {};
    T otherStuff;
    
    void setValues(int valueA, int valueB)
    {
        values.valueA = valueA;
        values.valueB = valueB;
    }
};

TEST(UnitTest, testA)
{
    Object<int> object;
    // do stuff that modified object's values via setValues()
    EXPECT_NE(object.values.valueA, 0);
    EXPECT_EQ(object.values.valueB, 0); 
}

Following is what I came up with but I get the following error

gmock-matchers.h:2074:31: error: no matching function for call to 'testing::internal::FieldMatcher<Object<int>::Values, int>::MatchAndExplainImpl(std::integral_constant<bool, false>::type, const Object<int>&, testing::MatchResultListener*&) const'
 2074 |     return MatchAndExplainImpl(
      |            ~~~~~~~~~~~~~~~~~~~^
 2075 |         typename std::is_pointer<typename std::remove_const<T>::type>::type(),
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 2076 |         value, listener);

If I were to use a member variable outside of a struct i.e testVar in MatchesStruct, it would compile fine. Why the complain with struct member?

using ::testing::Eq;
using ::testing::Ne;
using ::testing::Field;
using ::testing::AllOf;

template<typename T>
struct Object
{
    int testVar;
    struct Values
    {
        int valueA;
        int valueB;
    };
    Values values = {};
    T otherStuff;
    
    void setValues(int valueA, int valueB)
    {
        values.valueA = valueA;
        values.valueB = valueB;
    }
};

template <typename T, class M1, class M2>
auto MatchesStruct(M1 m1, M2 m2)
{
    return AllOf(Field(&Object<T>::Values::valueA, m1), 
                 Field(&Object<T>::Values::valueB, m2));
}

TEST(UnitTest, testA)
{
    Object<int> object;
    // do stuff that modified object's values via setValues()
    EXPECT_THAT(object, MatchesStruct<int>(Ne(0), Eq(0)));
}

Here's a live sample

xyf
  • 664
  • 1
  • 6
  • 16
  • using `ASSERT_THAT` helped solve the problem however ideally I wouldn't want to specify the struct member in the first argument and rather just the object itself. i.e just how I have for `EXPECT_THAT` in the description. Is it possible? https://godbolt.org/z/aEf4cafWa – xyf Oct 13 '22 at 00:38
  • like I said, I am ideally looking for something where I don't have to specify the struct member and rather just the struct alone. So instead of `EXPECT_THAT(object.values, MatchStruct(Ne(0), Eq(0)));`, ---> `EXPECT_THAT(object, MatchStruct(Ne(0), Eq(0)));` – xyf Oct 13 '22 at 00:48
  • possible to provide an example within the context? – xyf Oct 13 '22 at 00:57
  • please do so when you're on a desktop. Shall appreciate it :) – xyf Oct 13 '22 at 01:16

1 Answers1

1

This is almost the same question as the one you asked a few hours ago and is actually based on my initial answer there, but with the twist that here Object is a template and its members valueA and valueB are in another struct. You cannot form a member pointer to a member within a member variable like you attempt to do (i.e. &Object<T>::Values::valueA is not allowed by the C++ standard), so Field cannot be used that way.

In any case, the answer to this is almost the same as my answer in the other post (which I amended just now after you said in a comment there that your Object is a template): Write a proper matcher using MATCHER_P2, like so (live example):

template<typename T>
struct Object
{
    int ran;
    struct Values
    {
        int valueA = 42;
        int valueB = 0;
    };

    Values values = {};
    T otherStuff;
};

MATCHER_P2(MatchesStruct, m1, m2, "")
{
    return ExplainMatchResult(m1, arg.values.valueA, result_listener) 
        && ExplainMatchResult(m2, arg.values.valueB, result_listener);
}

TEST(UnitTest, testA)
{
    Object<int> object;
    EXPECT_THAT(object, MatchesStruct(Ne(0), Eq(0)));
}
Sedenion
  • 5,421
  • 2
  • 14
  • 42
  • interesting. Wouldn't there be a need for a template for MATCHER_P2? what's m1, m2? and `arg`? – xyf Oct 13 '22 at 16:01
  • `arg` is a reference to the object passed as first argument to `EXPECT_THAT` and it is implemented with a template, so that the matcher is as generic as possible. `m1` and `m2` are just arbitrary names for the two parameters. They are also templates. See the [documentation](https://google.github.io/googletest/gmock_cook_book.html#NewMatchers) for more information. – Sedenion Oct 13 '22 at 16:12
  • I see. Sorry I keep asking follow up but what if we have a class instead of a struct, meaning `Values` being a private member, and seemingly `MATCHER_P2` can't be set as a member function since the caller won't know about it?https://godbolt.org/z/j1Pnr1d3b – xyf Oct 13 '22 at 16:15
  • Uh, well, the easiest thing to do would be to provide a public accessor to the values. It is [debatable](https://stackoverflow.com/q/105007/3740047) if private data should be accessed in tests in the first place or not (where I personally lean towards only testing the public interface). Making a matcher a `friend` is I think not possible. – Sedenion Oct 13 '22 at 17:01
  • would you create public accessors to the values only for the sake of unit tests? there prolly should be a better solution where you're not exposing internal state publicly other than for unit tests and still be able to use `MATCHER`. Might open a separate thread – xyf Oct 13 '22 at 18:25
  • You could always write a global function `Object::Values const & GetValuesOf(Object const&)` or `std::pair GetValuesOf(Object const&)`, make that function a `friend` of `Object` and use `GetValuesOf()` in the matcher. – Sedenion Oct 13 '22 at 19:17