Mocking System APIs
One problem a lot of people run into is the need to test code that uses APIs they don’t control. This came up tonight during a discussion on mocking at the Pillar Technology Plugged In event in Ann Arbor. The system in question was a remote service, but I encountered it recently with UNIX system calls. Fortunately there’s a simple solution.
If you wrap the APIs in a class, you can mock that class. I’ll show you an example from my own project, written in C++. The first step was to create an interface. In classes that need to access this API, they will declare a member which is a pointer to that interface.
class DirectoryInterface {
public:
virtual void OpenDir(const char*) = 0;
virtual struct dirent* readdir() = 0;
virtual bool is_regular_file(const char *) = 0;
};
In my test suite, I create a mock (using Google Mock).
class MockDirectory : public DirectoryInterface {
public:
MOCK_METHOD1(OpenDir, void(const char*));
MOCK_METHOD0(readdir, struct dirent*());
MOCK_METHOD1(is_regular_file, bool(const char*));
};
Now in my test suite setup, I can inject the mock into my FileFolder object. I’ll also take this opportunity to set up some default behavior that I want to be true across all of my tests.
class testFileFolder : public testing::Test {
public:
FileFolder *folder;
NiceMock<MockDirectory> directory;
virtual void SetUp() {
folder = new FileFolder(".", &directory);
ON_CALL(directory, is_regular_file(StrEq(FILE_1)))
.WillByDefault(Return(true));
ON_CALL(directory, is_regular_file(StrEq(DIRECTORY_1)))
.WillByDefault(Return(true));
}
}
Now later on in a test, when I need to check how the FileFolder object interacts with the the Directory object, I can test that the FileFolder methods are making the right calls into the Directory interface, and control what the Directory interface returns to the FileFolder methods.
TEST_F(testFileFolder, files_withOneRegularFile_willPutFileInList)
{
// There will be an error is OpenDir is not called
EXPECT_CALL(directory, OpenDir(_));
// There will be an error if readdir is not called
EXPECT_CALL(directory, readdir())
.WillOnce(Return(&ent_file_1)); // when it is called, it should return ent_file_1
// Actually call the method we're trying to test
folder->Load();
// Check that the folder object has the expected state
ASSERT_THAT(folder->files.size(), Eq(1));
ASSERT_THAT(folder->files[0], StrEq(FILE_1));
}
This works especially well when you’re trying to test exception conditions. For instance I might want a test to ensure that I handle an unreadable or missing directory in a sane way. Instead of returning a value I could have just as easily thrown an exception, and written tests to ensure that the FileFolder object responded in the way I want.
In fact testing around error conditions is one of the most valuable things you can do with Test Driven Development. If for each API call you put in, you write a test around its possible error conditions, you’ll have a lot fewer bugs and production support calls.