c++googlemock

Why does gmock capture only work reliably with an added Invoke in DoAll of an ON_CALL


In very short: the issue I face is that in an

ON_CALL(...).WillByDefault(DoAll(..some capture..,Invoke([&captureValues]{..trace them...})));

the capture only works reliably if that "Invoke" is there. If I remove it, sometimes either the values are not captured correctly or they are not correctly passed on to the next expectation.

In detail: the productive code does the following:

HRESULT SomeClass::SomeMethod()
{
  HRESULT hr;
  int val1 = 4;
  int val2 = 7;
  hr = mockableObject.DoCall(val1, val2, &someOutObject);
  int someOutValue = 0;
  hr = someOutObject.DoOtherCall(&someOutValue);
  ...
  return hr;
}

DoCall returns an object, on that object DoOtherCall is called. val1 and val2 of DoCall shall be captured and used in the expactation of DoOtherCall to produce the returned result. The combination of DoCall and DoOtherCall on the returned object by DoCall is executed several times in SomeClass::SomeMethod (just if you wonder why the setup is like it is, it's the only way I found how to put these alternating calls under expectations).

The test looks as follows:

TEST_F(MySomeClassTest,
       MAKE_TEST_NAME(precondition_or_setup,
         tested_action,
         expected_result))
{
  // Arrange
  SetupTestCase();

  // Act
  SomeClass uut;
  HRESULT hr = uut.SomeMethod();

  // Assert
  EXPECT_EQ(hr, S_OK);
}

The setup for the test is the following:

void MySomeClassTest::SetupTestCase()
{
  uint16 capturedVal1 = 0;
  uint16 capturedVal2 = 0;

  ON_CALL(mockedObject, DoCall(_, _, _))
    .WillByDefault(
      DoAll(
        SaveArg<0>(&capturedVal1), // Capture value 1
        SaveArg<1>(&capturedVal2), // Capture value 2
        Invoke([&capturedVal1, &capturedVal2]{std::cout << "Values 1, 2: " << capturedVal1 << ", " << capturedVal2 << std::endl;}),
        SetArgPointee<2>(&someOtherMock),
        Return(S_OK)));

  EXPECT_CALL(someOtherMock, DoOtherCall(_))
    .WillRepeatedly(
      Invoke([&capturedVal1, &capturedVal2](int* returnValue) {
          std::cout << "Values 1, 2: " << capturedVal1 << ", " << capturedVal2 << std::endl;
          *returnValue = capturedVal1 * capturedVal2;
          return S_OK;
      }));
}

With the Invoke line in the ON_CALL the tests run green. If I remove the Invoke line (which basically I introduced just for being able to set a breakpoint and have a trace-output), not all captured values are correct in the EXPECT_CALL (there I trace the captured values as well).

More specifically: I know for sure DoCall's first two arguments are always > 0. But sometimes the second argument is 0 in the EXPECT_CALL's Invoke.

Anybody else faced this issue? Is it a gmock bug? Do I do something wrong?


Solution

  • Your test code has undefined behavior. Here:

    void MySomeClassTest::SetupTestCase()
    {
      uint16 capturedVal1 = 0;
      uint16 capturedVal2 = 0;
    
      ON_CALL(mockedObject, DoCall(_, _, _))
        .WillByDefault(
          DoAll(
            SaveArg<0>(&capturedVal1), // Capture value 1
            SaveArg<1>(&capturedVal2), // Capture value 2
            Invoke([&capturedVal1, &capturedVal2]{std::cout << "Values 1, 2: " << capturedVal1 << ", " << capturedVal2 << std::endl;}),
            SetArgPointee<2>(&someOtherMock),
            Return(S_OK)));
    

    You instruct mockObject by SaveArg<0>(&capturedVal1) SaveArg<1>(&capturedVal2) to store data in local variables capturedVal1 and capturedVal2 which lifetime ends at the time when mocked method is invoked.

    Move capturedVal1 and capturedVal2 to be a class fields.

    Final note:
    I no longer use SaveArg<0> and couple other gtest APIs. They are extremely useful when using C++03, but since C++11 it can be replaced by lambda, which is IMHO more handy.

    Anyway Why you need those variables at all? You do not use it to anything meaningfull accept printing (which bad). So you can just do this:

      ON_CALL(mockedObject, DoCall(_, _, _))
        .WillByDefault(Invoke([] (auto a, auto b, auto c) {
            std::cout << "Values 1, 2: " << a << ", " << b << std::endl;
            return S_OK;
         })
    

    You should not print anything to std::cout. This makes test very annoying, noisy and there other ways to print values in gtest, so those things are printed only when test fails.