6

I am using gtest to create unit tests to my c++ program. In my tests I have to write a lot of checks like this:

ASSERT_TRUE(myObject.IsValid());
EXPECT_EQ(myObject.GetSomeAttribute(), expectedValue);

I have to write both checks because if I omit the ASSERT_TRUE and myObject happened to be not valid, than myObject.GetSomeAttributre() call crashes. That's not good even in tests.

What I want is to write something like:

EXPECT_XXX_EQ(myObject.GetSomeAttribute(), expectedValue);

This line of code should do approximately the same as the original two lines (with optional bonus that if myObject is not valid, this will be reported, GetSomeAttribute() would not be called, but the test will continue running).

How can I write such custom assert/expect?

Justin
  • 24,288
  • 12
  • 92
  • 142
lesnik
  • 2,507
  • 2
  • 25
  • 24
  • Possible duplicate of [Custom C++ assert macro](https://stackoverflow.com/questions/5252375/custom-c-assert-macro) – AresCaelum Jul 21 '17 at 14:30
  • 1
    Why not fix your code to check that the object is valid in the call to getattribute? – stark Jul 21 '17 at 14:43
  • @stark Two reasons: 1. I can't modify GetSomeAttribute() 2. Even if I could, the best this method could do is to die if called for invalid object. – lesnik Jul 21 '17 at 14:54
  • @Eddge Not a duplicate; googletest asserts are very different from the standard c++ asserts – Justin Jul 21 '17 at 21:32
  • 1
    Did you not read [the documentation](https://github.com/google/googletest/blob/master/googletest/docs/AdvancedGuide.md#using-a-function-that-returns-an-assertionresult)? – Justin Jul 21 '17 at 21:37
  • Related: https://stackoverflow.com/questions/7120742/custom-expect-near-macro-in-google-test – Justin Jul 21 '17 at 21:49

2 Answers2

10

From the Advanced Guide, we can see that there are a couple ways we could do this.

The easiest way is by using assertions in a subroutine:

template<typename T>
void AssertAttributeEquals(MyObject const& obj, T value) {
    ASSERT_TRUE(obj.IsValid());
    // googletest has the assumption that you put the
    // expected value first
    EXPECT_EQ(value, obj.GetAttribute());
}

And you can call it like so:

AssertAttributeEquals(myObject, expectedValue);

Although you may want to use SCOPED_TRACE to get a better message on failure:

{
    SCOPED_TRACE("whatever message you want");
    AssertAttributeEquals(myObject, expectedValue);
}

Alternatively, you can use a function that returns an AssertionResult:

template<typename T>
::testing::AssertionResult AttributeEquals(MyObject const& obj, T value) {
    if (!obj.IsValid()) {
        // If MyObject is streamable, then we probably want to include it
        // in the error message.
        return ::testing::AssertionFailure() << obj << " is not valid";
    }
    auto attr = obj.GetAttribute();

    if (attr == value) {
        return ::testing::AssertionSuccess();
    } else {
        return ::testing::AssertionFailure() << attr << " not equal to " << value;
    }
}

This can be used like so:

EXPECT_TRUE(AttributeEquals(myObject, expectedValue));

This second technique has the benefit of producing nice error messages even if you don't use SCOPED_TRACE

Justin
  • 24,288
  • 12
  • 92
  • 142
  • 1
    The downside with functions is that in case of failure the reported source line is inside the function and not where the function is called. This makes it harder to see where exactly things failed. – Fred Schoen Aug 14 '18 at 14:05
  • The `SCOPED_TRACE` can help but is extra code one has to write. (had to create a new comment as I didn't edit quickly enough) – Fred Schoen Aug 14 '18 at 14:59
0

I could remove the need to duplicate all the calls to SCOPED_TRACE("...") by using macros. Now you can use the normal GTEST assertions inside a void function and use the macro in your tests:

test_common.h:

inline void AssertTokenTypesEqual__(const std::string& code, std::vector<TokenType> expectedTokens)
{
    auto tokens = Tokenize(code);

    ASSERT_EQ(tokens.size(), expectedTokens.size());
    for (int i = 0; i < tokens.size(); ++i) {
        ASSERT_EQ(expectedTokens[i], tokens[i].type);
    }
}

#define ASSERT_TOKEN_TYPES_EQUAL(code__, expectedTokens__)  \
    SCOPED_TRACE("Tokenization didn't match expectations"); \
    AssertTokenTypesEqual__(code__, expectedTokens__)

my-test.cpp:

TEST(TokenizerTest, Works) {
   auto code = "void func();";

   auto expectedTokens = {...}; // <- you might need to create variables of your values outside the assertion.

   ASSERT_TOKEN_TYPES_EQUAL(code, expectedTokens);
}
Ambrus Tóth
  • 552
  • 6
  • 14