Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Gmock - InSequence vs RetiresOnSaturation

I don't understand the following gmock example:

{
    InSequence s;

    for (int i = 1; i <= n; i++) {
        EXPECT_CALL(turtle, GetX())
        .WillOnce(Return(10*i))
        .RetiresOnSaturation();
    }
}

When I remove .RetiresOnSaturation() the above code works the same way - GetX returns 10, 20 and so on. What is the reason to use .RetiresOnSaturation() when we also use InSequence object ? Could you explain that ?

like image 763
Irbis Avatar asked Nov 01 '25 13:11

Irbis


2 Answers

In the exact example given RetiresOnSaturation() doesn't change anything. Once the final expectation in the sequence is saturated that expectation remains active but saturated. A further call would cause the test to fail.

RetiresOnSaturation() is generally used when overlaying expectations. For example:

class Turtle {
  public:
    virtual int GetX() = 0;
};

class MockTurtle : public Turtle {
  public:
    MOCK_METHOD0(GetX, int());
};

TEST(GmockStackoverflow, QuestionA)
{
    MockTurtle turtle;

    // General expectation - Perhaps set on the fixture class?
    EXPECT_CALL(turtle, GetX()).WillOnce(Return(0));

    // Extra expectation
    EXPECT_CALL(turtle, GetX()).WillOnce(Return(10)).RetiresOnSaturation();

    turtle.GetX();
    turtle.GetX();
}

This property can be used in combination with InSequence when the sequence of expected events overlays another expectation. In this scenario the last expectation in the sequence must be marked RetiresOnSaturation(). Note that only the last expectation needs to be marked because when an expectation in sequence is saturated it retires the prerequisite expectations.

The example below demonstrates how this might work out in practice. Removing RetiresOnSaturation() causes the test to fail.

TEST(GmockStackoverflow, QuestionB)
{
    MockTurtle turtle;

    EXPECT_CALL(turtle, GetX()).WillOnce(Return(0));

    {
        InSequence s;
        EXPECT_CALL(turtle, GetX()).WillOnce(Return(10));
        EXPECT_CALL(turtle, GetX()).WillOnce(Return(10)).RetiresOnSaturation();
    }

    turtle.GetX();
    turtle.GetX();
    turtle.GetX();
}

like image 81
Adam Casey Avatar answered Nov 04 '25 20:11

Adam Casey


From my experience, some (ok, possibly many) developers have a problem, like a gtest error message, discover that RetiresOnSaturation() makes the problem go away, and then get into the habit of liberally sprinkling RetiresOnSaturation() throughout their unit tests – because it solves problems. This is apparently easier than reasoning about what the test case is supposed to accomplish. On the other hand, I like to think in terms of what has to happen (according to the documented API contract) in what order – which can be a partial order if you use After() or don't have everything in the same sequence – and that makes more expressive constructs like InSequence or After() come naturally to my mind.

So, as Adam Casey stated, there is no technical reason, but IMO there could be an issue of magical thinking or insufficient training.

I recommend avoiding RetiresOnSaturation(). There are some general issues with it (like causing confusing warning messages, see example below), but mostly it is too low level when compared to the alternatives, and is almost never needed if you have clean contracts, and use the previously mentioned alternatives correctly. You could say it's the goto of gtest expectations…

Addendum A: Example of when a gratuitous RetiresOnSaturation() makes for worse messages, and yes, I have seen such code:

EXPECT_CALL(x, foo()).WillOnce(Return(42)).RetiresOnSaturation();

If x.foo() is called more than once, let's say twice, then, without RetiresOnSaturation(), you would have received an error message like "No matching expectation for foo() … Expected: to be called once … Actual: called twice (oversaturated)", which is about as specific as possible. But because of RetiresOnSaturation(), you will only get an "Unexpected function call foo()" warning, which is confusing and meaningless.

Addendum B: In your example, it is also possible that a refactoring to use InSequence was made after the fact, and the person doing the refactoring didn't realize that RetiresOnSaturation() was now redundant. You could do a "blame" in your version control system to check.

like image 44
Arne Vogel Avatar answered Nov 04 '25 20:11

Arne Vogel



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!