How to override the default ON_CALL action for just one EXPECT_CALL and go back to the default action later
Asked Answered
T

1

9

I would like to test the method of my system, whose return value partially depends on the return value of the call to some kind of connection interface. In most cases I would like the IConnection to return true upon any kind of call to it's open(_, _) method. Except in one case, when I explicitly test for the condition with failed connection.

Example:

/*
 * Some kind of network interface with method `open`
 */
class IConnection {
public:
    IConnection() = default;
    virtual ~IConnection() = default;
    virtual bool open(const std::string& address, int port) = 0;
};

class ConnectionMock: public IConnection {
public:
    MOCK_METHOD2(open, bool(const std::string& address, int port));
};

class MySystem {
public:
    MySystem() = delete;
    MySystem(std::shared_ptr<IConnection> connection): connection_(connection) {}
    bool doSth() {
        /*
         * Do some things, but fail if connection fails
         */
        bool connectionStatus = connection_->open("127.0.0.1", 6969);
        if (!connectionStatus) {
            return false;
        }
        // do other things
        return true;
    }
private:
    std::shared_ptr<IConnection> connection_;
};

TEST(MySystemShould, returnFalseIfFailedToOpenConnectionAndTrueIfSucceeded) {
    auto connectionMock = std::make_shared<NiceMock<ConnectionMock> >();
    ON_CALL(*connectionMock, open(_, _)).WillByDefault(Return(true));
    MySystem system(connectionMock);
    // if I don't specify Times test fill fail, because WillOnce automatically sets Times(1)
    EXPECT_CALL(*connectionMock, open(_, _)).Times(AnyNumber()).WillOnce(Return(false));
    /*
     * Commented code below is not a good solution - after expectation retires
     * the test will fail upon subsequent calls
     */
    //EXPECT_CALL(*connectionMock, open(_, _)).WillOnce(Return(false)).RetiresOnSaturation();
    ASSERT_FALSE(system.doSth());
    /*
     * Code bellow allows me to avoid the warning
     */
    //EXPECT_CALL(*connectionMock, open(_, _)).WillRepeatedly(Return(true));
    ASSERT_TRUE(system.doSth());
}

The problems with my current solution is that when the EXPECT_CALL override becomes saturated, even though gmock goes back to the default action specified on ON_CALL, every subsequent call to open(_, _) is causing the following warning:

GMOCK WARNING:
/whatever.cpp:105: Actions ran out in EXPECT_CALL(*connectionMock, open(_, _))...
Called 2 times, but only 1 WillOnce() is specified - taking default action specified at:
/whatever.cpp:103:

even though I'm using NiceMock. I can get rid of the warning by specifying EXPECT_CALL with WillRepeatedly(Return(true)), but this is the duplication of my code in ON_CALL.

I would like to know, how can I override the default action specified with ON_CALL for just one call to IConnection::open, and then go back to the defaults, without causing gmock to print a warning. The perfect solution would be something similar to:

EXPECT_CALL(*connectionMock, open(_, _)).WillOnce(Return(false)).DisableExpectationAfterSaturation();

but it doesn't exist. RetiresOnSaturation doesn't work as I would like, because it fails the test after getting saturated (doesn't match action specified with ON_CALL).

Twi answered 3/7, 2018 at 16:26 Comment(0)
R
9

EDIT 2
The DoDefault() - feature comes close to what is asked in the question. It specifies that an action in EXPECT_CALL should go back to the default action specified by ON_CALL:

using ::testing::DoDefault;

// Default action
ON_CALL(*connectionMock, open(_, _)).WillByDefault(Return(true));

// returns true once and then goes back to the default action
EXPECT_CALL(*connectionMock, open(_, _)
  .WillOnce(Return(false))
  .WillRepeatedly(DoDefault());

Initial answer

If the return value of IConnection::open depends on the parameters you can specify ON_CALL twice but with different arguments (or rather arguments instead of the placeholder):

ON_CALL(*connectionMock, open(_, _)).WillByDefault(Return(true));
ON_CALL(*connectionMock, open("BAD_ADDRESS", 20)).WillByDefault(Return(false));

So any time the mocked method open will be called with arguments "BAD_ADDRESS" and 20, it will return false, and true otherwise.

Here is a simple example:

using ::testing::_;
using ::testing::AnyNumber;
using ::testing::Return;

class A {
 public:
  virtual bool bla(int a) = 0;
};

class MOCKA : public A {
 public:
  MOCK_METHOD1(bla, bool(int));
};

TEST(ABC, aBABA) {
  MOCKA a;
  ON_CALL(a, bla(_)).WillByDefault(Return(false));
  ON_CALL(a, bla(1)).WillByDefault(Return(true));

  EXPECT_CALL(a, bla(_)).Times(AnyNumber());

  EXPECT_TRUE(a.bla(1));
  EXPECT_TRUE(a.bla(1));
  EXPECT_TRUE(a.bla(1));
  EXPECT_FALSE(a.bla(2));
  EXPECT_FALSE(a.bla(3));
  EXPECT_FALSE(a.bla(4));
}

EDIT 1 I think now I understood the problem and if I did then the solution is very simple:

EXPECT_CALL(*connectionMock, open(_, _))
      .Times(AnyNumber())
      .WillOnce(Return(true))
      .WillRepeatedly(Return(false));

When ConnectionMock::open will be called inside of MySystem::doSth it will once return true and then always return false no matter what the arguments are. In this case you also don't need to specify ON_CALL. Or do you definitely need to specify the actions with ON_CALL instead of EXPECT_CALL?

Rodrigo answered 19/7, 2018 at 14:14 Comment(7)
Thanks for the answer. Yes, I know that it's possible to specify different actions for different mock method arguments matchers. But it is not applicable in my case. My system always uses correct address:port and the goal is to test if after a failure to open a connection system is still in a good state to try again and succeed later.Twi
@Ptaq666 please check the edit in my answer. Is that what the behaviour you need?Rodrigo
Yup, I am also aware of the solution with single EXPECT_CALL specifying first action with WillOnce and subsequent actions with WillRepeatedly. But this is different then overriding default action in ON_CALL. Common practice is to specify ON_CALL (I know that you can also use EXPECT_CALL AnyNumber for that) default action in the test fixture constructor (because maybe my system will call open, maybe not) and later use EXPECT_CALL in the particular TEST_F if there is a need to override the defaults. Being able to do it seems a desired behavior in gtest for me.Twi
@Ptaq666 Ok, now I understand. But don't really know how it can be done. What comes to my mind and is closest to that is maybe DoDefault(): ` EXPECT_CALL(*connectionMock, open(_, _)).WillOnce(Return(true)).WillRepeatedly(DoDefault());` This returns true once and then returns to the default action specified by ON_CALL.Rodrigo
O! Exactly this (DoDefault). I couldn't find this feature in gmock docs. It answers the "go back to the default action later" part of my question. You can post it in your answer.Twi
@Ptaq666 Glad It helps :) There isn't really much about it in the docs, so no wonder you could not find it :D. Added a new edit (at the top) in my answerRodrigo
I find DoDefault, but don't know how to use it. This save my time.Portal

© 2022 - 2024 — McMap. All rights reserved.