Ignoring mock calls during setup phase
Asked Answered
O

3

6

I often face the problem that mock objects need to be brought in a certain state before the "interesting" part of a test can start.

For example, let's say I want to test the following class:

struct ToTest
{
    virtual void onEnable();
    virtual void doAction();
};

Therefore, I create the following mock class:

struct Mock : ToTest
{
    MOCK_METHOD0(onEnable, void());
    MOCK_METHOD0(doAction, void());
};

The first test is that onEnable is called when the system that uses a ToTest object is enabled:

TEST(SomeTest, OnEnable)
{
    Mock mock;
    // register mock somehow

    // interesting part of the test
    EXPECT_CALL(mock, onEnable());
    EnableSystem();
}

So far, so good. The second test is that doAction is called when the system performs an action and is enabled. Therefore, the system should be enabled before the interesting part of the test can start:

TEST(SomeTest, DoActionWhenEnabled)
{
    Mock mock;
    // register mock somehow

    // initialize system
    EnableSystem();

    // interesting part of the test
    EXPECT_CALL(mock, doAction());
    DoSomeAction();

}

This works but gives an annoying warning about an uninteresting call to onEnable. There seem to be two common fixes of this problem:

  1. Using NiceMock<Mock> to suppress all such warnings; and
  2. Add an EXPECT_CALL(mock, onEnable()) statement.

I don't want to use the first method since there might be other uninteresting calls that really should not happen. I also don't like the second method since I already tested (in the first test) that onEnable is called when the system is enabled; hence, I don't want to repeat that expectation in all tests that work on enabled systems.

What I would like to be able to do is say that all mock calls up to a certain point should be completely ignored. In this example, I want expectations to be only checked starting from the "interesting part of the test" comment.

Is there a way to accomplish this using Google Mock?

Onega answered 30/3, 2014 at 14:46 Comment(0)
O
5

The annoying thing is that the necessary functions are there: gmock/gmock-spec-builders.h defines Mock::AllowUninterestingCalls and others to control the generation of warnings for a specific mock object. Using these functions, it should be possible to temporarily disable warnings about uninteresting calls.

That catch is, however, that these functions are private. The good thing is that class Mock has some template friends (e.g., NiceMock) that can be abused. So I created the following workaround:

namespace testing
{
// HACK: NiceMock<> is a friend of Mock so we specialize it here to a type that
// is never used to be able to temporarily make a mock nice. If this feature
// would just be supported, we wouldn't need this hack...
template<>
struct NiceMock<void>
{
    static void allow(const void* mock)
    {
        Mock::AllowUninterestingCalls(mock);
    }

    static void warn(const void* mock)
    {
        Mock::WarnUninterestingCalls(mock);
    }

    static void fail(const void* mock)
    {
        Mock::FailUninterestingCalls(mock);
    }
};

typedef NiceMock<void> UninterestingCalls;
}

This lets me access the private functions through the UninterestingCalls typedef.

Onega answered 27/11, 2014 at 14:55 Comment(0)
J
1

The flexibility you're looking for is not possible in gmock, by design. From the gmock Cookbook (emphasis mine):

[...] you should be very cautious about when to use naggy or strict mocks, as they tend to make tests more brittle and harder to maintain. When you refactor your code without changing its externally visible behavior, ideally you should't need to update any tests. If your code interacts with a naggy mock, however, you may start to get spammed with warnings as the result of your change. Worse, if your code interacts with a strict mock, your tests may start to fail and you'll be forced to fix them. Our general recommendation is to use nice mocks (not yet the default) most of the time, use naggy mocks (the current default) when developing or debugging tests, and use strict mocks only as the last resort.

Unfortunately, this is an issue that we, and many other developers, have encountered. In his book, Modern C++ Programming with Test-Driven Development, Jeff Langr writes (Chapter 5, on Test Doubles):

What about the test design? We split one test into two when we changed from a hand-rolled mock solution to one using Google Mock. If we expressed everything in a single test, that one test could set up the expectations to cover all three significant events. That’s an easy fix, but we’d end up with a cluttered test.

[...]

By using NiceMock, we take on a small risk. If the code later somehow changes to invoke another method on the [...] interface, our tests aren’t going to know about it. You should use NiceMock when you need it, not habitually. Seek to fix your design if you seem to require it often.

Jaimeejaimes answered 30/3, 2014 at 18:15 Comment(5)
Thanks for the clarification. However, I don't see why this isn't possible by design. It seems to me that all I'm asking for is a more fine-grained version of NiceMock. Do you think I have a wrong mindset by wanting this? (I don't have much experience in TDD so I imagine I could.) As a side note, if the Mock::*UninterestingCalls methods were part of the public API, I would've been able to implement this myself.Onega
@Job Google designed NiceMocks essentially with your use-case in mind (otherwise they'd be of very limited use). Whenever I encounter this type of situation, I've found it usually (but not always) indicates that I have an object that's trying to fill multiple roles. If I split the object into two different objects -one for each role- it gives me the finer-grained control you're describing: I instantiate the "interesting" object as a naggy mock, and the "uninteresting" object as a NiceMock.Jaimeejaimes
I see your point. Still, I keep having the feeling that my example should be a common problem: before you can test if an object does something when it is in a certain state, it should first be brought into that state. In this case, the object really cannot (and should not) be split into two different objects. The only "clean" way to implement this test seems to be to temporarily make the mock object nice. Or do I miss something here?Onega
I understand what you're saying: in order to make sure a length() function returns the right value, your test would need to invoke an add() or something similar. However, this is usually only necessary when you're testing the object that defines those functions; not when you're mocking it. When dealing with Role Interfaces, this is usually avoidable. My point is: this is indeed a common problem, but one that is often solved by refactoring our design rather than working around it in our tests.Jaimeejaimes
I'll try to come back from the Land of Abstraction, and address the problem at hand. :-) Consider if your ToTest struct had a method named enableSystemAndDoAction. This would let you sidestep the issue completely, since the initialization of the system just becomes an implementation detail of the collaborator.Jaimeejaimes
A
-1

You might be better off using a different mock class for your second test.

class MockOnAction : public ToTest {
    // This is a non-mocked function that does nothing
    virtual void onEnable() {}   

    // Mocked function
    MOCK_METHOD0(doAction, void());
}

In order for this test to work, you can have onEnable do nothing (as shown above). Or it can do something special like calling the base class or doing some other logic.

virtual void onEnable() {
    // You could call the base class version of this function
    ToTest::onEnable();

    // or hardcode some other logic
    // isEnabled = true;
}
Ambsace answered 27/11, 2014 at 14:13 Comment(1)
The problem with this solution is that all calls to onEnable are ignored. I would like to only ignore calls during the initialization phase of the test. That is, I want to temporarily ignore calls to onEnable.Onega

© 2022 - 2024 — McMap. All rights reserved.