Overview
It is very important to keep unit test small, simple, fast, and self-contained. A unit test that relies on another server or some other complicated modules to be running is simply not a unit test. To help with that, DI is a great practice, and mock objects a great tool. At first, it is not easy to get your head around the concept of mock and how to use them though. Like every power tool, they can also explode in your hands and for example make you believe you have tested something when in reality you have not. Making sure that the behaviour and input/output of mock objects reflects the reality is paramount.
PS, Given that we have never interacted with a real server or a external big module at the unit test level, it is important to write Integration Tests that will make sure our application is able to talk to the sort of servers it will deal with in real life.
A simple case
First, let's have a simple sample about how to write the code of unit test. Supposed that we had a chat service module which took charge of chat functions, such as "sendMessage", "onMessageReceived". Sample code as followings.
class IChatEngine
{
public:
...
virtual bool sendPrivateMessage(const char *pMessage) = 0;
virtual bool sendMessage(const char *pMessage) = 0;
virtual bool fire_onMessage(const char *pMessage) = 0;
...
};
class ChatService : public IServiceBase
{
public:
...
ChatService(IChatEngine* engine) {
m_chatEngine = engine;
m_chatEngine = COMMON_MESSAGE;
}
virtual bool sendMessage(const char *pMessage) {
if (pMessage == NULL) {
return false;
}
if (MESSAGE_SIZE_LIMIT_CHK(pMessage) != OK) {
return false;
}
bool result = false;
if (m_actionType == PRIVATE_MESSAGE) {
result = m_chatMgr->sendPrivateMessage(pMessage);
} else {
result = m_chatMgr->sendMessage(pMessage);
}
return result;
}
virtual bool onMessage(const char *pMessage) {
if (pMessage == NULL) {
return false;
}
return m_chatMgr->fire_onMessage(pMessage);
}
void setActionType(int type) {
m_actionType = type;
}
...
private:
IChatEngine* m_chatEngine;
int m_actionType;
};
Let's think about how to write the unit test code of ChatService class.
ChatService relies on the implementation of IChatEngine. Maybe you will involve the real object of IChatEngine and test the real action of ChatService. However, please consider the following questions firstly.
- If the implementation of "IChatEngine" is not available except the interface, how to do the unit testing for ChatService?
- The real actions of ChatEngine will involve lots of functions including connecting to the server, sending message to server, receive the message from the server and so on, are you sure it is necessary to involve them in the unit testing of ChatService?
- Your test target is ChatService, Do you think you do focus on "ChatService"?
- If you involve lots of actions, is it suitable for a unit test case and is it a unit test case?
So, let's use googlemock to help us to implement the unit test for ChatService and experience how to write a real unit test case.
(About googlemock, please refer to http://code.google.com/p/googlemock/wiki/Documentation)
#include "gmock/gmock.h"
#include "gtest/gtest.h"
class MockChatEngine : public IChatEngine {
public:
...
MOCK_METHOD1(sendPrivateMessage, bool(const char *pMessage));
MOCK_METHOD1(sendMessage, bool(const char *pMessage));
MOCK_METHOD1(Fire_onMessage, bool(const char *pMessage));
};
using ::testing::Return;
TEST(ChatServiceTest, sendMessage) {
MockChatEngine chatEng;
// expect sendPrivateMessage will be called twice, first time it will return true, second time is false.
EXPECT_CALL(chatEng, sendPrivateMessage())
.Times(2)
.WillOnce(Return(true))
.WillOnce(Return(false));
// expect sendMessage will be called twice, first time it will return true, second time is false.
EXPECT_CALL(chatEng, sendMessage())
.Times(2)
.WillOnce(Return(true))
.WillOnce(Return(false));
ChatService chatsvr(&chatEng);
bool result = chatsvr.sendMessage(NULL);
EXCEPT_TRUE(result == false);
result = chatsvr.sendMessage(/*size overflow message*/);
EXCEPT_TRUE(result == false);
result = chatsvr.sendMessage("hello chat service!");
EXCEPT_TRUE(result == true);
result = chatsvr.sendMessage("hello chat service!");
EXCEPT_TRUE(result == false);
chatsvr.setActionType(PRIVATE_MESSAGE);
bool result = chatsvr.sendMessage(NULL);
EXCEPT_TRUE(result == false);
result = chatsvr.sendMessage(/*size overflow message*/);
EXCEPT_TRUE(result == false);
result = chatsvr.sendMessage("hello chat service!");
EXCEPT_TRUE(result == true);
result = chatsvr.sendMessage("hello chat service!");
EXCEPT_TRUE(result == false);
}
TEST(ChatServiceTest, onMessage) {
MockChatEngine chatEng;
// expect fire_onMessage will be called twice, first time it will return true, second time is false.
EXPECT_CALL(chatEng, fire_onMessage())
.Times(2)
.WillOnce(Return(true))
.WillOnce(Return(false));
ChatService chatsvr(&chatEng);
bool result = chatsvr.onMessage(NULL);
EXCEPT_TRUE(result == false);
result = chatsvr.onMessage("hello chat service!");
EXCEPT_TRUE(result == true);
result = chatsvr.onMessage("hello chat service!");
EXCEPT_TRUE(result == false);
}
int main(int argc, char** argv) {
::testing::InitGoogleMock(&argc, argv);
return RUN_ALL_TESTS();
}
We use googlemock framework to mock the action of ChatEngine in the above sample, so we will not need to care the implementation outside the ChatService class and just focus on the interface and input/output of ChatService. This is a simple, separated unit test.