Search code examples
c++unit-testinggoogletest

Wrapping the EXPECT_NE, EXPECT_EQ into a validation function


I have a few unit tests that validate whether certain values equate to 0 or not. In some cases, they're supposed to be 0 and in some, they're not.

Just like the following: testA expects valueA to not contain 0 whereas testB expects valueB to not.

What I am looking to do is to somehow wrap the validation part in a function so instead of invoking EXPECT_NE/EXPECT_EQ for each member, I just invoke a function that takes care of the validation part.

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

TEST(UnitTest, testB)
{
    Object object;
    // do stuff that modified object's values
    EXPECT_EQ(object.valueA, 0);
    EXPECT_NE(object.valueB, 0); 
}

This is what I came up with but it's a bit too verbose. Wondering if there's a better approach to it?

void Validate(Object* obj, bool valA, bool valB)
{
    // verify valueB
    if (valA)
    {
        EXPECT_EQ(object->valueA, 0);
    }
    else
    {
        EXPECT_NE(object->valueA, 0);
    }

    // verify valueB
    if (valB)
    {
        EXPECT_EQ(object->valueB, 0); 
    }
    else
    {
        EXPECT_NE(object->valueB, 0); 
    }
}

TEST(UnitTest, testA)
{
    Object object;
    // do stuff that modified object's values
    Validate(&object, false, true);
}

TEST(UnitTest, testB)
{
    Object object;
    // do stuff that modified object's values
    Validate(&object, true, false);
}

Solution

  • With FieldsAre and structured binding

    With C++17 and a recent GoogleTest version (>= v1.12.0), you can simply use FieldsAre(), in case Object allows structured binding (see live example):

    using ::testing::FieldsAre;
    using ::testing::Eq;
    using ::testing::Ne;
    
    struct Object
    {
        int valueA;
        int valueB;
    };
    
    TEST(UnitTest, testA)
    {
        Object object{42,0};
        EXPECT_THAT(object, FieldsAre(Ne(0), Eq(0)));
    }
    
    TEST(UnitTest, testB)
    {
        Object object{0, 42};
        EXPECT_THAT(object, FieldsAre(Eq(0), Ne(0)));
    }
    

    With a combination of matchers

    Otherwise (if your GoogleTest is too old or Object does not allow structured binding), you can write a simple matcher-like function:

    using ::testing::Field;
    using ::testing::AllOf;
    
    template <class M1, class M2>
    auto MatchesValues(M1 m1, M2 m2)
    {
        return AllOf(Field(&Object::valueA, m1), Field(&Object::valueB, m2));
    }
    

    and use it just like FieldsAre (live example):

    TEST(UnitTest, testA)
    {
        Object object{42,0};
        EXPECT_THAT(object, MatchesValues(Ne(0), Eq(0)));
    }
    
    TEST(UnitTest, testB)
    {
        Object object{0, 42};
        EXPECT_THAT(object, MatchesValues(Eq(0), Ne(0)));
    }
    

    With a custom matcher

    As noted in the comment, your original Object is a template, in which case Field cannot be used. In this case you can write a proper customer matcher like so (live example):

    template<typename T>
    struct Object
    {
        int valueA;
        int valueB;
        T otherStuff;
        
        Object(int a, int b) : valueA(a), valueB(b) {}
    };
    
    MATCHER_P2(MatchesValues, m1, m2, "")
    {
        return ExplainMatchResult(m1, arg.valueA, result_listener) 
            && ExplainMatchResult(m2, arg.valueB, result_listener);
    }
    
    TEST(UnitTest, testA)
    {
        Object<int> object{42,0};
        EXPECT_THAT(object, MatchesValues(Ne(0), Eq(0)));
    }