Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c++

googlemock

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).

like image 986
pptaszni Avatar asked Jul 03 '18 16:07

pptaszni


People also ask

What is difference between On_call and Expect_call?

If you are also in the habit of giving tests descriptive names that tell what they verify, you can often easily guess what's wrong just from the test log itself. So use ON_CALL by default, and only use EXPECT_CALL when you actually intend to verify that the call is made.

What is EXPECT_ CALL in gmock?

EXPECT_CALL not only defines the behavior, but also sets an expectation that the method will be called with the given arguments, for the given number of times (and in the given order when you specify the order too).


1 Answers

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?

like image 181
Mike van Dyke Avatar answered Nov 15 '22 08:11

Mike van Dyke