Google Test Framework does not print std::optional

Viewed 1081

I have a data structure which is being notified to a listener. Among its many variables, it has an optional double which represents an angle:

struct Data
{
    // ...other data fields
    std::optional<double> angle;
    // ...other data fields
};

I want to check that the listener is being called with the right angle. I am using the Google Test Framework, so I have defined my own matcher here:

MATCHER_P( HasAngle, angle, "" )
{
    if ( !arg.angle.has_value( ) )
    {
        return false;
    }

    return arg.angle.value( ) == angle;
}

And checked if my listener is being called with the expected value.

EXPECT_CALL( listener, on_event( HasAngle( 45.0 ) ) ).Times( AtLeast( 1 ) );

It ended up that the listener is not called with the expected value, but the printouts are really unreadable:

  Expected arg #0: has angle 45
       Actual: 128-byte object <75-85 4D-52 FB-21 F9-BF 47-D9 48-53 FB-21 09-C0 00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-00 01-17 23-83 FD-7F 00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-00>

The actual value is unintelligible. I was not expecting the framework to not support C++17 types by default, so I started reading the Google Test Framework Cookbook and I tried to add my own PrintTo overload like this:

void
PrintTo( const std::optional< double >& opt, std::ostream* os )
{
    if ( !opt )
    {
        *os << "std::nullopt";
    }
    else
    {
        *os << *opt;
    }
}

Needless to say, it did not help. What I am pretty much sure is that since the output states 128-byte object it is trying to print the sole std::optional<double> and not the entire struct Data, which is much bigger than that, but it is not succeeding despite the overloaded PrintTo.

4 Answers

GoogleTest does print std::optional values, if you use the Optional(m) matcher.

Example test:

TEST_F(MyClassTests, myFunctionTest)
{
    std::optional<std::string> actual_value = "foo";
    std::string expected_value = "bar";

    EXPECT_THAT(actual_value, testing::Optional(expected_value));
}

The output will be similar, but after the cryptic 128-byte object <75-85 4D-52 FB-21 F9-BF... text, the contained value will be printed additionally:

...
[ RUN      ] MyClassTests.myFunctionTest
myclasstests.cpp:123: Failure
Value of: actual_value
Expected: value is equal to "bar"
  Actual: 40-byte object <DE-AD BE-EF CA-FE BA-BE ...>, whose value "foo" doesn't match
[  FAILED  ] MyClassTests.myFunctionTest (0 ms)
...

If you expect a std::optional to be std::nullopt in a testcase, you will most probably do it with:

EXPECT_EQ(actual_value, std::nullopt);

The test will fail, if actual_value actually has a value. But again, the actual value will not be printed. As a workaround, you can implement the test like this:

if (actual_value) {
    FAIL() << "actual_value is not std::nullopt, but: " << actual_value.value();
}

Your attempt does not work as it is a std:: type. You would have to define the PrintTo in std namespace according to documentation

// If you can't declare the function in the class it's important that PrintTo() // is defined in the SAME namespace that defines Bar. C++'s look-up rules rely // on that.

So I would expect the following to work (if included before the google test framework):

namespace std {
void
PrintTo( const optional< double >& opt, std::ostream* os )
{
    if ( !opt )
    {
        *os << "std::nullopt";
    }
    else
    {
        *os << *opt;
    }
}
}

You're worrying about the wrong type. Your struct Data is the result being tested, not a std::optional<double>. That's why your code has arg.angle, because arg is a Data. Define a stream insertion operator for Data, then gtest will use it.

Definition of standard output operator for your type in the test should help and be used in gmock error messages

template <class T>
std::ostream & operator<<(std::ostream & os, std::optional<T> const & opt)
{
  if (opt) return os << opt.value();
  return os << "EMPTY";
}
Related