Categories

Using a Mock Library to make Unit Testing Easier in C++

Unit testing is the process of writing tests that test a single *unit* of code. This is as opposed to integration tests which test multiple pieces of code together. The problem often comes up in which you want to write unit tests for some code A, but that code calls into some other code B, and you don’t want to be testing B in your unit tests. B should have it’s own unit tests or B is out of your control like an OS API. A’s unit tests should only test that A does what you expect it to do given that the code it calls does what it’s expected to do.

A common solution to this is to turn B into an interface and provide two implementations. The real implementation and a mock implementation. In the unit tests for A you use the Mock implementation of B. The problem with that is solution is it can be a lot of work to write a mock implementation of B. Work that itself could be the source of bugs and work that effects your schedule.

So, people have written mock libraries or mock frameworks that help make it easier to write these mock classes. I only have experience with Google’s C++ mock library, googlemock or gmock, but from reading around the net about other mock libraries I get the impression that googlemock is one of the best so I guess I was lucky to have this be my first experience with mocks.

To give a simple example, assume you have some code that calls fopen, fwrite and fclose.

// Writes a file, returns true on success.
bool WriteFile(const char* filename, const void* data, size_size) {
   FILE* file = fopen(filename, "wb");
   if (!file) {
     return false;
   }

   if (fwrite(data, 1, size, file) != size) {
     fclose(fp);
     return false;
   }

   if (fclose(file) != 0) {
     return false;
   }

   return true;
}

The first thing we need to do is change the code to use an interface instead of directly calling fopen, fwrite and fclose. Here’s our interface class

class FileIOInterface {
public:
  ~virtual FileIOInterface() {}

  virtual FILE* Open(const char* filename, const char* mode) = 0;
  virtual size_t Write(const void* data, size_t size, size_t num, FILE* file) = 0;
  virtual int Close(FILE* file) = 0;
};

And here’s the actual implementation of that class

class FileIO : public FileIOInterface {
public:
  virtual FILE* Open(const char* filename, const char* mode) {
    return fopen(filename, mode);
  }

  virtual size_t Write(const void* data, size_t size, size_t num, FILE* file) {
    return fwrite(data, size, num, file);
  }

  virtual int Close(FILE* file) {
    return fclose(file);
  }
};

Here’s the original function refactored to work through the interface.

// Writes a file, returns true on success.
bool WriteFile(FileIOInterface fio, 
               const char* filename, const void* data, size_size) {
   FILE* file = fio.Open(filename, "wb");
   if (!file) {
     return false;
   }

   if (fio.Write(data, 1, size, file) != size) {
     return false;
   }

   if (fio.Close(file) != 0) {
     return false;
   }

   return true;
}

Next we use the googlemock library to create a Mock version of the same interface. We’l use this mock version in our unit tests.

class MockFileIO : public FileIOInterface {
public:
  virtual ~MockFileIO() { }

  MOCK_MEHTOD2(Open, FILE*(const char* filename, const char* mode));
  MOCK_METHOD4(Write, size_t(const void* data, 
                             size_t size, size_t num, FILE* file));
  MOCK_METHOD1(Close, int(FILE* file));
};

The googlemock library basically provides a framework so we can check for all calls coming into the mock class and decide what to do, what value to return or what side effects to make happen.

Here’s the unit tests using the mock we just created. I don’t have to provide a test implementation of Open/Write/Close. googlemock handles that for me.

TEST(WriteFileTest, SuccessWorks) {
  MockFileIO fio;
      
  static char data[] = "hello";
  const char* kName = "test";
  File test_file;

  // Tell the mock to expect certain calls and what to 
  // return on those calls.
  EXPECT_CALL(fio, Open(kName, "wb")
      .WillOnce(Return(&test_file));
  EXPECT_CALL(fio, Write(&data, 1, sizeof(data), &test_file))
      .WillOnce(Return(sizeof(data)));
  EXPECT_CALL(file, Close(&test_file))
      .WillOnce(Return(0));
      
  EXPECT_TRUE(WriteFile(kName, &data, sizeof(data)));
}

TEST(WriteFileTest, FailsIfOpenFails) {
  MockFileIO fio;
      
  static char data[] = "hello";
  const char* kName = "test";
  File test_file;

  // Tell the mock to expect certain calls and what to 
  // return on those calls.
  EXPECT_CALL(fio, Open(kName, "wb")
      .WillOnce(Return(NULL));
      
  EXPECT_FALSE(WriteFile(kName, &data, sizeof(data)));
}

TEST(WriteFileTest, FailsIfWriteFails) {
  MockFileIO fio;
      
  static char data[] = "hello";
  const char* kName = "test";
  File test_file;

  // Tell the mock to expect certain calls and what to 
  // return on those calls.
  EXPECT_CALL(fio, Open(kName, "wb")
      .WillOnce(Return(&test_file));
  EXPECT_CALL(fio, Write(&data, 1, sizeof(data), &test_file))
      .WillOnce(Return(0));
      
  EXPECT_FALSE(WriteFile(kName, &data, sizeof(data)));
}

TEST(WriteFileTest, FailsIfCloseFails) {
  MockFileIO fio;
      
  static char data[] = "hello";
  const char* kName = "test";
  File test_file;

  // Tell the mock to expect certain calls and what to 
  // return on those calls.
  EXPECT_CALL(fio, Open(kName, "wb")
      .WillOnce(Return(&test_file));
  EXPECT_CALL(fio, Write(&data, 1, sizeof(data), &test_file))
      .WillOnce(Return(sizeof(data)));
  EXPECT_CALL(file, Close(&test_file))
      .WillOnce(Return(EOF));
      
  EXPECT_FALSE(WriteFile(kName, &data, sizeof(data)));
}

Note that I didn’t have to provide a mock implementation of fopen/fwrite/fclose. googlemock handles this for me.

You can make the mock strict if you want. A Strict mock will fail the tests if any function that is not expected is called or if any function that is expected is called with the wrong arguments. Googlemock provides a ton of helpers and adapters so you generally don’t need to write much code to get the mock to do what you want. It takes a few days to learn the different adapters but if you’re using it often they quickly become second nature.

Someone mentioned that fopen/fwrite/fclose was too easy and asked for a better example. I asked them to name the api and they said how about FindFirstFile, FindNextFile, FindClose.

Here’s an example using FindFirstFile, FindNextFile, FindClose.

First the interface

class FindFileInterface {
public:
 virtual HANDLE FindFirstFile(
    LPCTSTR lpFileName,
    LPWIN32_FIND_DATA lpFindFileData) = 0;
    
  virtual BOOL FindNextFile(
    HANDLE hFindFile,
    LPWIN32_FIND_DATA lpFindFileData) = 0;
    
 virtual BOOL FindClose(
    HANDLE hFindFile) = 0;
    
  virtual DWORD GetLastError(void) = 0;
};

Then the actual implementation

class FindFileImpl : public FindFileInterface {
public:
  virtual HANDLE FindFirstFile(
      LPCTSTR lpFileName,
      LPWIN32_FIND_DATA lpFindFileData) {
    return ::FindFirstFile(lpFileName, lpFindFileData);
  }
    
  virtual BOOL FindNextFile(
      HANDLE hFindFile,
      LPWIN32_FIND_DATA lpFindFileData) {
    return ::FindNextFile(hFindFile, lpFindFileData);
  }
    
  virtual BOOL FindClose(
      HANDLE hFindFile) {
    return ::FindClose(hFindFile);
  }
    
  virtual DWORD GetLastError(void) {
    return ::GetLastError();
  }
};

The Mock using gmock

class MockFindFile : public FindFileInterface {
public:
  MOCK_METHOD2(FindFirstFile,
               HANDLE(LPCTSTR lpFileName, LPWIN32_FIND_DATA lpFindFileData));
  MOCK_METHOD2(FindNextFile,
               BOOL(HANDLE hFindFile, LPWIN32_FIND_DATA lpFindFileData));
  MOCK_METHOD1(FindClose, BOOL(HANDLE hFindFile));
  MOCK_METHOD0(GetLastError, DWORD());
};

The function I we wanted to test, already refactored to use FindFileInterface.

DWORD PrintListing(FindFileInterface* findFile, const TCHAR* path) {
  WIN32_FIND_DATA ffd;
  HANDLE hFind;
    
  hFind = findFile->FindFirstFile(path, &ffd);
  if (hFind == INVALID_HANDLE_VALUE) {
     printf ("FindFirstFile failed");
     return 0;
  }
    
  do {
    if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
       _tprintf(TEXT("  %s   \n"), ffd.cFileName);
    } else {
      LARGE_INTEGER filesize;
      filesize.LowPart = ffd.nFileSizeLow;
      filesize.HighPart = ffd.nFileSizeHigh;
      _tprintf(TEXT("  %s   %ld bytes\n"), 
               ffd.cFileName, filesize.QuadPart);
    }
  } while(findFile->FindNextFile(hFind, &ffd) != 0);
    
  DWORD dwError = findFile->GetLastError();
  if (dwError != ERROR_NO_MORE_FILES) {
    _tprintf(TEXT("error %d"), dwError);
  }
    
  findFile->FindClose(hFind);
  return dwError;
}

The unit tests using the mock.

#include 
#include 

using ::testing::_;
using ::testing::Return;
using ::testing::DoAll;
using ::testing::SetArgumentPointee;

// Some data for unit tests.
static WIN32_FIND_DATA File1 = {
  FILE_ATTRIBUTE_NORMAL,  // DWORD    dwFileAttributes;
  { 123, 0, },            // FILETIME ftCreationTime;
  { 123, 0, },            // FILETIME ftLastAccessTime;
  { 123, 0, },            // FILETIME ftLastWriteTime;
  0,                      // DWORD    nFileSizeHigh;
  123,                    // DWORD    nFileSizeLow;
  0,                      // DWORD    dwReserved0;
  0,                      // DWORD    dwReserved1;
  { TEXT("foo.txt") },    // TCHAR   cFileName[MAX_PATH];
  { TEXT("foo.txt") },    // TCHAR    cAlternateFileName[14];
};
    
static WIN32_FIND_DATA Dir1 = {
  FILE_ATTRIBUTE_DIRECTORY,  // DWORD    dwFileAttributes;
  { 123, 0, },            // FILETIME ftCreationTime;
  { 123, 0, },            // FILETIME ftLastAccessTime;
  { 123, 0, },            // FILETIME ftLastWriteTime;
  0,                      // DWORD    nFileSizeHigh;
  123,                    // DWORD    nFileSizeLow;
  0,                      // DWORD    dwReserved0;
  0,                      // DWORD    dwReserved1;
  { TEXT("foo.dir") },    // TCHAR   cFileName[MAX_PATH];
  { TEXT("foo.dir") },    // TCHAR    cAlternateFileName[14];
};
    
TEST(PrintListingTest, TwoFiles) {
  const TCHAR* kPath = TEXT("c:\\*");
  const HANDLE kValidHandle = reinterpret_cast(1234);
  MockFindFile ff;

  // Tell the mock what calls to expect and what to do with each one.    
  EXPECT_CALL(ff, FindFirstFile(kPath, _))
    .WillOnce(DoAll(SetArgumentPointee<1>(Dir1),
                    Return(kValidHandle)));
  EXPECT_CALL(ff, FindNextFile(kValidHandle, _))
    .WillOnce(DoAll(SetArgumentPointee<1>(File1),
                    Return(TRUE)))
    .WillOnce(Return(FALSE));
  EXPECT_CALL(ff, GetLastError())
    .WillOnce(Return(ERROR_NO_MORE_FILES));
  EXPECT_CALL(ff, FindClose(kValidHandle));
    
  PrintListing(&ff, kPath);
}
    
TEST(PrintListingTest, OneFile) {
  const TCHAR* kPath = TEXT("c:\\*");
  const HANDLE kValidHandle = reinterpret_cast(1234);
  MockFindFile ff;
    
  // Tell the mock what calls to expect and what to do with each one.    
  EXPECT_CALL(ff, FindFirstFile(kPath, _))
    .WillOnce(DoAll(SetArgumentPointee<1>(Dir1),
                    Return(kValidHandle)));
  EXPECT_CALL(ff, FindNextFile(kValidHandle, _))
    .WillOnce(Return(FALSE));
  EXPECT_CALL(ff, GetLastError())
    .WillOnce(Return(ERROR_NO_MORE_FILES));
  EXPECT_CALL(ff, FindClose(kValidHandle));
    
  PrintListing(&ff, kPath);
}
    
TEST(PrintListingTest, ZeroFiles) {
  const TCHAR* kPath = TEXT("c:\\*");
  MockFindFile ff;
    
  // Tell the mock what calls to expect and what to do with each one.    
  EXPECT_CALL(ff, FindFirstFile(kPath, _))
    .WillOnce(Return(INVALID_HANDLE_VALUE));
    
  PrintListing(&ff, kPath);
}
    
TEST(PrintListingTest, Error) {
  const TCHAR* kPath = TEXT("c:\\*");
  const HANDLE kValidHandle = reinterpret_cast(1234);
  MockFindFile ff;
    
  // Tell the mock what calls to expect and what to do with each one.    
  EXPECT_CALL(ff, FindFirstFile(kPath, _))
    .WillOnce(DoAll(SetArgumentPointee<1>(Dir1),
                    Return(kValidHandle)));
  EXPECT_CALL(ff, FindNextFile(kValidHandle, _))
    .WillOnce(Return(FALSE));
  EXPECT_CALL(ff, GetLastError())
    .WillOnce(Return(ERROR_ACCESS_DENIED));
  EXPECT_CALL(ff, FindClose(kValidHandle));
    
  PrintListing(&ff, kPath);
}

Notice I didn’t have to implement any of the mock functions.

While mocks are closely related to unit testing they are not the same thing. Google has a unit testing library, googletest or gtest. You can use googlemock with googletest or you can use it with any testing framework.


Note: This is a repost of a answer I left on stackoverflow.com