A GMock Example using an Interface class design, a Sequence class and the ElementsAreArray() method

by Jan Gabriel | 08 Feb 2021

last edited: 11 Feb 2021

C++ GTest GMock plantuml


I like Test-Driven Development (TDD) because it gives me a goal to develop towards as well as peace of mind when I make changes to my code later on. TDD describes a development strategy where you write a test, which will pass when certain conditions are met, followed by the code which takes your test input data and returns the expected output data. In this post, I'm going to use Google's test framework (GTest: release-1.10.0).

I'll discuss the following:

  • Using GMock to test an interface class;
  • Using a Sequence to enable the use of multiple EXPECT_CALL() implementations; and
  • Using GMock's ElementsAreArray() matcher to test a C-Style array.

The code used in this post is available at gist.github.com.

Testing an interface with GMock

Designing your classes to connect with each other via an interface class improves the testability of your classes. In our example, the Socket class will implement the SocketInterface class. The RequestHandler takes a reference to a SocketInterface at construction time, handles a request and calls the interface accordingly. The three classes are set up as follows:

// Abstract class
class SocketInterface {
public:
    virtual void Write(const std::string &buffer) = 0;
    ~SocketInterface() = default;
};

// The Socket class implements the SocketInterface's Write() method
class Socket : public SocketInterface {
public:
    Socket(const std::string &address, int port);
    void Write(const std::string &buffer) override;
    ~Socket() = default;
};

// Our RequestHandler handles a request and calls the provided SocketInterface
class RequestHandler {
public:
    // Contructor sets the SocketInterface dependency
    RequestHandler(SocketInterface& socket_interface)
        : socket_interface_(socket_interface) { }
    // Handle our request and act accordingly
    bool HandleRequest(const std::string& buffer) {
        bool result = false;
        if (buffer == "Hello World once!" ||
            buffer == "Hello World twice!") {
            // Call the SocketInterface!
            socket_interface_.Write(buffer);
            result = true;
        }
        return result;
    }
private:
    SocketInterface& socket_interface_;
    static int request_id_;
};

Let's take a look at our scenario. I'll explain it at the hand of an UML Sequence diagram written in plantuml:

Sequence Diagram

In the example above, our ClassUser creates a new RequestHandler and sets the dependency on its SocketInterface instance. When the ClassUser calls the HandleRequest() method, the RequestHandler will call the Write() method, if the data is supported else it will return a failure.

The problem is that with a typical test we only test the returned value of the method we call on the RequestHandler. We cannot see the Write() method called on the SocketInterfce from our test's point of view for example:

// Scenario 1, Test 1: Testing without using a MockInterface
TEST(RequestHandlerTestCase, TestWithoutMockInterface) {
    // ++ Setup our classes ++
    // The Socket class implements the SocketInterface class
    Socket socket;
    // The Request handler analyses our test input data and acts accordingly
    RequestHandler request_handler(socket);
    // ++ Act and Assert our calls ++
    EXPECT_TRUE(request_handler.HandleRequest("Hello World once!"));

    // By this point the socket_interface.Write() method 
    // has already been called - How do we test this?
}

Our solution is the use of GMock, a part of Google's Test Framework. GMock has the ability to "Mock" an interface. This enables us to test expected calls to an interface. Let's take a look at our updated class declaration:

// The MockSocket class implements the SocketInterface's Write() method
// But this time using GMock's Method Macro
class MockSocket : public SocketInterface {
public:
    MOCK_METHOD(void, Write, (const std::string& buffer), (override));
    ~MockSocket() = default;
};

We'll use the updated MockSocket in our scenario. This time we also submit a couple of extra calls to the HandleRequest() method. GMock will now also assert the calls made to the SocketInterface's Write() method.

// Scenario 1, Test 2 : Testing with a MockInterface and expecting a Sequence
TEST(RequestHandlerTestCase, TestWithMockInterface) {
    // ++ Setup our classes ++
    // The MockSocket class implements the SocketInterface class
    MockSocket mock_socket;
    // The Request handler analyses our test input data and acts accordingly
    RequestHandler request_handler(mock_socket);
    // To ensure our expectations happen in the order we specified we use a sequence
    testing::Sequence sequence;
    // ++ Setup Our Expected Call ++
    EXPECT_CALL(mock_socket, Write("Hello World once!"))
        .Times(1) // We expect it to be called once
        .InSequence(sequence);
    EXPECT_CALL(mock_socket, Write("Hello World twice!"))
        .Times(2) // We expect it to be called twice
        .InSequence(sequence);
    EXPECT_CALL(mock_socket, Write("Hello World once!"))
        .Times(1) // We expect it to be called once again
        .InSequence(sequence);

    // ++ Act and Assert our calls ++
    EXPECT_TRUE(request_handler.HandleRequest("Hello World once!"));
    EXPECT_TRUE(request_handler.HandleRequest("Hello World twice!"));
    EXPECT_TRUE(request_handler.HandleRequest("Hello World twice!"));
    EXPECT_TRUE(request_handler.HandleRequest("Hello World once!"));

    // By this point the socket_interface.Write() method 
    // has already been called, but now GMock would have asserted 
    // our specified expectations
}


Using a Sequence to enable the use of multiple EXPECT_CALL() implementations

The above example uses a Sequence. When setting up your test cases you sometimes want to test a sequence of calls with the exact same expectation e.g. sending an ACK. When you do not use a sequence you will receive the following error:

..\..\..\main.cc(96): error: Mock function called more times than expected - returning directly.
    Function call: Write(@000000F0D2B7F4E8 "Hello World once!")
         Expected: to be called once
           Actual: called twice - over-saturated and active
..\..\..\main.cc(90): error: Actual function call count doesn't match EXPECT_CALL(mock_socket, Write("Hello World once!"))...


The use of a Sequence class will thus help you to test specific expectations in a sequence.

Using the GMock's ElementsAreArray() matcher to test a C-Style array

GMock works well when testing expected strings, but how do you test a c-style byte array? This is also supported, however, there is a different approach needed. Some changes were made to the classes for this example. Take a look at the use of the ElementsAreArray() method below:

// Scenario 2, Test 1 : Expecting multiple calls using a Sequence
TEST(RequestHandlerTestCase, TestWithSequence) {
    // Declare our c-style buffer
    const char c_style_buffer[4] = { 0x00, 0x01, 0x02, 0x03 };
    // ++ Setup our classes ++
    // The MockSocket class implements the SocketInterface class
    MockSocket mock_socket;
    // The Request handler analyses our test input data and acts accordingly
    RequestHandler request_handler(mock_socket);
    // To ensure our expectations happen in the order we specified we use a sequence
    testing::Sequence sequence;
    // ++ Setup Our Expected Calls ++
    EXPECT_CALL(mock_socket, Write(
        testing::StrEq("Hello World once!"),
            strlen("Hello World once!")))
            .Times(1) // We only expect it to be called once
        .InSequence(sequence);

    // THIS IS IMPORTANT! //
    EXPECT_CALL(mock_socket, Write(
        testing::_, // We now allow any value for the first parameter
        sizeof(c_style_buffer)))
        // We declare our c-style buffer here
        .With(testing::ElementsAreArray<char>(c_style_buffer))
            .Times(1) // We only expect it to be called once
        .InSequence(sequence);

    // Adding another call to show the work of the sequence
    EXPECT_CALL(mock_socket, Write(
        testing::StrEq("Hello World once!"),
        strlen("Hello World once!")))
        .Times(1) // We only expect it to be called once
        .InSequence(sequence);

    // ++ Act and Assert our calls ++
    EXPECT_TRUE(request_handler.HandleRequest("Hello World once!",
        strlen("Hello World once!")));
    EXPECT_TRUE(request_handler.HandleRequest(c_style_buffer,
        sizeof(c_style_buffer)));
    EXPECT_TRUE(request_handler.HandleRequest("Hello World once!",
        strlen("Hello World once!")));

    // By this point the socket_interface.Write() method 
    // has already been called, but now GMock would have asserted 
    // our specified expectation for both cases
}

GMock will assert at the address value of the c_style_buffer if you do not use the GMock's ElementsAreArray() method. In this case you will probably receive the following error:

unknown file: error:
Unexpected mock function call - returning directly.
    Function call: Write(0000011B4CFC5030 pointing to "", 4)
Google Mock tried the following 2 expectations, but none matched:

..\..\..\main.cc(199): tried expectation #0: EXPECT_CALL(mock_socket, Write( testing::StrEq("Hello World once!"), strlen("Hello World once!")))...
  Expected arg #0: is equal to "Hello World once!"
           Actual: 0000011B4CFC5030 pointing to ""
  Expected arg #1: is equal to 17
           Actual: 4
         Expected: to be called once
           Actual: called once - saturated and active
..\..\..\main.cc(204): tried expectation #1: EXPECT_CALL(mock_socket, Write( c_style_buffer, sizeof(c_style_buffer)))...
  Expected arg #0: is equal to 00000018D1BDEF94 pointing to ""
           Actual: 0000011B4CFC5030 pointing to ""
         Expected: to be called once
           Actual: never called - unsatisfied and active
..\..\..\main.cc(204): error: Actual function call count doesn't match EXPECT_CALL(mock_socket, Write( c_style_buffer, sizeof(c_style_buffer)))...
         Expected: to be called once
           Actual: never called - unsatisfied and active

Conclusion

When writing unit tests it is important to setup your classes properly. Through designing the correct interface classes and using GMock, it is possible to test the expected calls on your interfaces.

You can also test byte arrays with ElementsAreArray() and even setup expected sequences with using the testing::Sequence class and InSequence() method.

The code used in this post is available at gist.github.com.

Feel free to get in touch via the contact page with any comments or questions on the post.

Happy Coding.