Skip to content

Repository files navigation

microtest.h

A small, dependency-free, header-only C++11 unit testing framework. Copy include/microtest/microtest.h into your project, or use the CMake target microtest::microtest.

#include <microtest/microtest.h>

TEST(Addition) {
  ASSERT_EQ(2 + 2, 4);
  ASSERT_STRNEQ("hello", "world");
}

TEST_MAIN();

Define TEST_MAIN() in exactly one source file. Tests can live in multiple source files linked into the same executable; give each test a unique name. See examples/basic.cpp for all assertions.

Requirements

  • C++11 or newer, with exceptions enabled.
  • No runtime libraries beyond the C++ standard library.
  • For repository development: CMake 3.23+ and Ninja (used by the presets). Other CMake generators work with a manual configure command.

Copying the header does not require CMake, Ninja, or Python.

Integrating with CMake

As a source subdirectory:

add_subdirectory(external/microtest)
add_executable(my_tests tests.cpp)
target_link_libraries(my_tests PRIVATE microtest::microtest)

FetchContent_MakeAvailable(microtest) also exposes the same target. Pin the repository to a reviewed commit or release when using FetchContent. Tests and examples are disabled by default when microtest is included as a subproject. The library does not impose warning or sanitizer flags on consumers.

To install a package:

cmake -S . -B build/package -DMICROTEST_BUILD_TESTS=OFF -DMICROTEST_BUILD_EXAMPLES=OFF
cmake --install build/package --prefix /your/install/prefix

Then configure the consumer with -DCMAKE_PREFIX_PATH=/your/install/prefix:

find_package(microtest 0.1 CONFIG REQUIRED)
add_executable(my_tests tests.cpp)
target_link_libraries(my_tests PRIVATE microtest::microtest)

Assertions and execution

Macro Passes when
ASSERT(condition), ASSERT_TRUE(condition) The condition is true
ASSERT_FALSE(condition) The condition is false
ASSERT_NULL(value) The pointer is null
ASSERT_NOTNULL(value) The pointer is not null
ASSERT_EQ(a, b) a == b
ASSERT_NEQ(a, b) a != b
ASSERT_STREQ(a, b) String contents are equal
ASSERT_STRNEQ(a, b) String contents differ

Each argument is evaluated once. Assertions work as single statements, including in unbraced if/else branches. C++11 does not specify the evaluation order between two arguments; do not modify the same object in both operands. Equality operands must support the relevant comparison. Diagnostics print each operand using operator<< when available, or <unprintable> otherwise. String assertions accept std::string and non-null C strings.

A failed assertion throws mt::AssertFailedException, ends the current test, and reports its expression, source file, and line. The runner continues with the remaining tests. Other std::exception instances fail the current test and report their what() message; non-standard exceptions fail it with a generic diagnostic. The runner continues after either kind of exception. Crashes are not recovered. Tests run sequentially in registration order: declaration order within a source file, with order across source files unspecified. Tests must not depend on that cross-file order. TEST functions have internal linkage, so duplicate names in different source files are reported by the runner instead of failing at link time.

Test names must be unique across the entire executable, including different namespaces. AddTest() returns false for a duplicate and records a registration error. An invalid suite reports the duplicate names and runs no tests. TEST_MAIN() prints a registration-error summary and exits with status 1; RunAllTests() returns the registration-error count in this case. Custom runners can distinguish this from test failures using RegistrationErrorCount().

TEST_MAIN() returns 0 when all tests pass (including an empty suite), and 1 when assertions fail or tests throw. mt::TestsManager::RunAllTests() returns the actual failure count. mt::Runtime::args() exposes command-line arguments, including the executable name. All runner diagnostics, including assertion values, use the stream passed to RunAllTests(file) (stdout by default). Assertions store value diagnostics in the exception; code catching mt::AssertFailedException directly can inspect them with getDetails().

Colors default to mt::ColorMode::Auto: each output stream uses ANSI colors only when connected to a terminal. Redirected files and pipes, including captured CI logs, stay plain. On Windows, terminal detection uses the CRT's _isatty; the terminal must support ANSI escape sequences. On platforms without terminal detection, auto mode uses plain output.

Custom runners can override the policy before running tests:

mt::setColorMode(mt::ColorMode::Never);  // Always disables ANSI colors.
// mt::setColorMode(mt::ColorMode::Always);  // Includes colors even in a pipe/file.
// mt::setColorMode(mt::ColorMode::Auto);    // Restores terminal detection.
return mt::TestsManager::RunAllTests() == 0 ? 0 : 1;

getColorMode() returns the current policy. Colors affect presentation only; test names, source locations, and summary wording are the same in every mode.

Development

cmake --preset dev
cmake --build --preset dev
ctest --preset dev

The dev preset enforces C++11. Use modern for C++23, or sanitizers for AddressSanitizer and UndefinedBehaviorSanitizer with GCC/Clang. Each preset has its own build directory. make test wraps the dev workflow.

Tests independently check assertions, operand evaluation, expression precedence, conditional use, source diagnostics, registration across translation units, runner exit codes and summaries, and an installed, relocated CMake package. The public header is also compiled without prerequisite includes.

Format C++ files with clang-format 18.1.8 using the checked-in configuration. CI checks formatting and builds with GCC, Clang, Apple Clang, and MSVC, including a C++23 job and a Linux sanitizer job.

Build options:

  • MICROTEST_BUILD_TESTS: regression tests; defaults on for standalone builds.
  • MICROTEST_BUILD_EXAMPLES: passing examples; defaults on for standalone builds.
  • MICROTEST_SANITIZERS: development instrumentation; defaults off.

The header moved from src/ to include/; update manual include search paths from -Isrc to -Iinclude. The public include spelling remains unchanged.

License

MIT; see LICENSE.

About

C++ single-header-only unit testing framework

Topics

Resources

Stars

6 stars

Watchers

4 watching

Forks

Releases

Packages

Contributors

Languages