Mocking
Sometimes you need to unit test code that wasn’t written with testability in mind — for example, a plain “Hello, world!” program:
#include <stdio.h>
int main(void) { puts("Hello, world!"); return 0;}Testing main() poses two problems:
- You need to call
main()from a test, while keeping the originalmain()callable — so there must be a way to override the entry point the linker/loader uses while still reaching the real function. main()has an outgoing side effect (puts()). To verify it, you either replaceputs()with a mock that records what was passed, or intercept stdout/stderr directly.
AceUnit’s examples show five different ways to solve this, each with different toolchain requirements. All five achieve the same test; they differ only in how they intercept main and puts.
| Strategy | Renames main via |
Intercepts puts via |
Portable to |
|---|---|---|---|
| objcopy override | objcopy --redefine-sym |
objcopy --redefine-sym |
GNU/Clang toolchains |
| Link-time wrap | ld --wrap=main |
ld --wrap=puts |
GNU ld / LLVM lld |
| Compile-time macro | -Dmain=original_main |
-Dputs=mocked_puts |
Any compiler |
| Darwin interposer | objcopy --redefine-sym |
DYLD_INSERT_LIBRARIES dynamic interposition |
macOS/Darwin only |
| Stdout/stderr interception | objcopy --redefine-sym |
pipe + read back STDOUT | POSIX systems |
If you’re not sure which to pick: compile-time macro requires nothing beyond a standard compiler and works everywhere. Link-time wrap is the cleanest if you’re already committed to GNU/Clang toolchains and don’t want to touch object files with objcopy.