Skip to content

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:

  1. You need to call main() from a test, while keeping the original main() callable — so there must be a way to override the entry point the linker/loader uses while still reaching the real function.
  2. main() has an outgoing side effect (puts()). To verify it, you either replace puts() 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.