Once you know how, it’s pretty easy to write unit tests and mock system functions in C++ using GoogleTest.
But getting all the information together in one place is a bit tricky and there are a couple of gotchas. This article gives a brief overview.
System Functions
So what are system functions anyway? These are the APIs which your operating system provides, such as open()
, read()
, close()
, etc. Most non-trivial code bases will call various system functions in order to implement its functionality.
Intercepting system functions
There are multiple approaches to testing code which calls system functions:
- Place all system calls into wrapper classes. You now have a standard C++ class which you can mock in the normal way. The con is that you have to rewrite the code to use the wrapper class instead of calling the system functions directly.
- Use precompiler tricks to overwrite the calls to the system functions. This is only possible if you can inject the precompiler definitions into the code to be tested and recompile.
- Use the linker
--wrap=
option to wrap the functions you’re interested in. This is the approach we’ll use in this article as it is the least intrusive to the codebase and can even be used on precompiled object files.
Wrapping function calls
In order to wrap a function, linkers provide the --wrap=<function>
command line parameter. This is a quite common approach when coding in C and wanting to trace or instrument system function calls.
When specified, each call to function
is replaced with a call to __wrap_function
instead. The real function is still available via __real_function
. It is common to call the real function from within the wrap function as well as adding some additional functionality.
For example:
#include <dirent.h>
int __wrap_stat(const char *filename, struct stat *stat)
{
printf("stat(%s, %p) called\n", filename, stat);
return __real_stat(filename, stat);
}
C++ Gotcha! Name Mangling
In C++ there’s a gotcha with this approach – name mangling! All C++ symbols get name-mangled to ensure that they don’t conflict. But when using the linker “wrap” feature, the names must be exactly as above. Luckily C++ provides the extern "C"
mechanism to turn name mangling off for sections of code.
So in C++ we can do the following:
#include <dirent.h>
#include "mocks/dir_mock.hpp"
extern "C" {
int __wrap_stat(const char *filename, struct stat *stat)
{
printf("stat(%s, %p) called\n", filename, stat);
// Invoke the mock if it is active
if (FileMock::instance != nullptr) {
return FileMock::instance->stat(filename, stat);
}
// Otherwise call the real implementation
return __real_stat(filename, stat);
}
} // extern "C"
Linker wrap flags using CMake
CMake is a popular meta-build system and as of CMake v3.13, it is fairly easy to provide linker flags to the build process using the target_link_options()
setting.
cmake_minimum_version(3.13)
add_executable(my_test ...)
target_link_options(my_test
PRIVATE
LINKER:--wrap=stat
LINKER:--wrap=...
)
Writing the mock
With the wrapped functions above and the build system configured correctly, it is now pretty easy to create a mock class for use in the unit tests:
class FileMock {
static *FileMock instance;
DirMock() { instance = this; }
virtual ~FileMock() { instance = nullptr; }
MOCK_METHOD(int, stat, (const char *filename, struct stat *stat));
}
Add more mock methods as required, making sure to provide the wrapped functions as well.
NOTE: The static instance
member must be declared in an implementation file somewhere; the file containing the wrap functions is a good candidate.
Writing the tests
We can now write unit tests which use the mock class instead of running against the real system.
TEST(StatTests, test_stat_real_directory) {
struct stat s{};
ASSERT_EQ(0, stat("/tmp", &s));
ASSERT_TRUE(S_ISDIR(s.st_mode));
ASSERT_NE(0, stat("/invalid_directory", &s));
}
TEST(StatTests, test_stat_mock_directory) {
FileMock fm;
EXPECT_CALL(fm, stat(::testing::StrEq("valid_dir"), ::testing::_)
.WillOnce([](const char* path, struct stat* s) {
if (s) {
memset(stat, 0x00, sizeof(*s));
s->st_mode |= S_IFDIR;
}
return 0;
});
EXPECT_CALL(fm, stat(::testing::StrEq("invalid_dir"), ::testing::_)
.WillOnce([](const char* path, struct stat* s) {
errno = ENOENT;
return 1;
});
struct stat s{};
int rv = stat("valid_dir, &s);
ASSERT_EQ(rv, 0);
ASSERT_TRUE(S_ISDIR(s.st_mode));
rv = stat("invalid_dir, &s);
ASSERT_NE(rv, 0);
ASSERT_EQ(errno, ENOENT)
}
As the example shows, it is trivially easy to select between invoking the real implementation versus the mock implementation simply by constructing, or not, an instance of the mock class in the unit test.
C++ Gotcha! Mock Parameter Validation
In the example above we see (or rather, don’t see, as it’s already been compensated for) another C++ Gotcha: parameter validation in EXPECT_CALL
mock function calls.
It is essential that the correct Matcher is used to validate parameters. In the case above, if the StrEq()
Matcher wasn’t being used, C++ wouldn’t compare the strings, as one might expect, but it would compare the pointers! Even though the pointers point to the same string value they are different and hence the mock call would fail.
Wrapping up
So in summary, to unit test your code with mocked system calls:
- Wrap the system call(s) which your code invokes
- Write a mock class containing mock methods for each system call
- Implement the wrapped system call(s) to call the mock method
- …
- Win!
Example
The inspiration for this article was the research I did to write a small Directory Iterator class. The full example with unit tests is available here.