How to pass extra argument to google mock EXPECT_CALL

61 views Asked by At

I'm testing My C++ class that uses a dependency via an interface. I have mocked the interface and have a complex lambda that I use in GMock's EXPECT_CALL to verify that my class calls the mocked function correctly. Function is called several times during one call to the API of my class. The correct behavior is different in different calls, so I would like to supply my checker a different expected result in different calls.

How can I give different expected result for my lambda in EXPECT_CALL(..).WillOnce(lambda)? Meaning something like this:

EXPECT_CALL(..)
  .WillOnce(InvokeWithExtraArgs(lambda, expectedResult1)),
  .WillOnce(InvokeWithExtraArgs(lambda, expectedResult2)),

Full example:

#include "gtest/gtest.h"
#include "gmock/gmock.h"

using namespace testing;

struct Dependency
{
    virtual void foo (double d) = 0;
};

struct DependencyMock : public Dependency
{
    MOCK_METHOD (void, foo, (double), (override));
};

struct Service
{
    Service (Dependency *d) : dep {d} {};
    void doStuff ()
    {
        dep->foo (1.2);
        dep->foo (2.3);
    }
    Dependency *dep;
};

TEST (mytest, test1)
{
    DependencyMock depMock;
    Service service {&depMock};

    auto fooVerifier = [] (double d /*, int expectedResult - is this possible? */ )
    {
        auto result = d + 123;     // some lengthy logic here to deduce if foo() was called correctly
        auto expectedResult = 234; // <<< how to get this as an argument
        EXPECT_EQ (result, expectedResult);
    };

    EXPECT_CALL (depMock, foo (_))
        .Times(2)
        .WillOnce (Invoke (fooVerifier))   // <<< How can I give expected result for above EXPECT_EQ here?
        .WillOnce (Invoke (fooVerifier));  // <<< How can I give expected result for above EXPECT_EQ here?
    service.doStuff ();
}

int main (int argc, char *argv[])
{
    ::testing::InitGoogleTest (&argc, argv);
    return RUN_ALL_TESTS ();
}
2

There are 2 answers

0
Yksisarvinen On BEST ANSWER

Sounds like you want a matcher, not a special Invoke.

MATCHER_P(FooArgIsCorrect, expected, "")
{
    auto result = arg + 123; // your length calculation go here
    return result == expected;
}

Then, in test:

EXPECT_CALL (depMock, foo (FooArgIsCorrect(FirstExpectedValue))).RetiresOnSaturation();
EXPECT_CALL (depMock, foo (FooArgIsCorrect(SecondExpectedValue))).RetiresOnSaturation();

If your real case has something that needs to be calculated when the function is called (because e.g. your return value depends on it), you can still use Invoke with lambdas, just pass the extra argument via capture list:

    auto fooVerifier = [] (double d, int expectedResult)
    {
        auto result = d + 123;     // some lengthy logic here to deduce if foo() was called correctly
        auto expectedResult = 234; // <<< how to get this as an argument
        EXPECT_EQ (result, expectedResult);
    };

    EXPECT_CALL (depMock, foo (_))
        .Times(2)
        .WillOnce (Invoke ([expected](double d){return fooVerifier(d, expected);}))  
        .WillOnce (Invoke ([expected](double d){return fooVerifier(d, expected);}));  
1
pptaszni On

Not sure if I understood correctly your matcher requirements, but your problem can be addressed in a very generic way. You are allowed to invoke any logic on EXPECT_CALL action, so:

TEST (mytest, test1)
{
    DependencyMock depMock;
    Service service {&depMock};
    int scenarioCounter = 0; // can be std::atomic_int if your system would use some async operations
    auto verifyResult1 = [] (double d) { return true; };  // impl your logic
    auto verifyResult2 = [] (double d) { return true; };  // impl your logic
    EXPECT_CALL(depMock, foo (_)).WillRepeatedly([&](double d) {
        if (scenarioCounter == 0 && !verifyResult1(d)) {
            throw std::runtime_error("explain what happened");
        }
        else if (scenarioCounter == 1 && !verifyResult2(d)) {
            throw std::runtime_error("explain what happened again");
        }
        scenarioCounter++;
    });
    service.doStuff();  // ASSERT_NO_THROW if you prefer
}

Configure just 1 EXPECT_CALL (gmock expectations on the same function with different args are not very user-friendly), and implement different verification actions, dependent on your current system state. In the example I used scenarioCounter to keep track of that state. Instead of gtest assertions you can throw the exception to immediately exit and fail the testcase - gtest has default try-catch block around the tests, so it will explain why the test failed, but alternatively you can ASSERT_NO_THROW. Instead of throwing, you can also flip some boolean flag, or simply call FAIL();.

Let me know if I understood your requirements correctly.