How to Write a New Unit Test
This project uses the Erbsland Unit Test system as its testing framework. This short guide shows how to add a new unit test.
File Structure
Unit tests go in
test/unittest/srcTest data belongs in
test/unittest/dataThe framework and its documentation live in
test/unittest/erbsland-unittest
Basic Test Structure & Naming
The framework automatically registers test classes and methods—if you follow these naming rules:
Class names must end in
...Test(e.g.,IntegerTest)Filenames must match the class name and go in
test/unittest/src(e.g.,IntegerTest.cpp)You must add the file to
test/unittest/src/CMakeLists.txtTest methods must be named
test...(), returnvoid, and take no parameters
Minimal Example
For a class Example, create ExampleTest.cpp like this:
// Copyright (c) 2025 Tobias Erbsland - https://erbsland.dev
// SPDX-License-Identifier: Apache-2.0
#include <erbsland/unittest/all.hpp>
TESTED_TARGETS(Example)
class ExampleTest : public el::UnitTest {
public:
    Example example;
    void testConstruction() {
        example = Example{};
        REQUIRE(example.empty());
    }
};
You can optionally use TESTED_TARGETS(...) or TAGS(...) to add metadata for filtering and test selection.
Test Macros
Here’s what you can use to verify results:
REQUIRE(expr)REQUIRE_FALSE(expr)REQUIRE_THROWS(expr)REQUIRE_THROWS_AS(exception_class, expr)REQUIRE_NOTHROW(expr)REQUIRE_EQUAL(actual, expected)REQUIRE_NOT_EQUAL(a, b)REQUIRE_LESS(a, b)REQUIRE_LESS_EQUAL(a, b)REQUIRE_GREATER(a, b)REQUIRE_GREATER_EQUAL(a, b)
If you’re testing custom types, make sure they can be formatted using std::format:
template <>
struct std::formatter<Example> : std::formatter<std::string> {
    auto format(const Example &value, format_context& ctx) const {
        return std::formatter<std::string>::format(value.toString(), ctx);
    }
};
Add Extra Info on Test Failures
To make test failures easier to diagnose, you can either override additionalErrorMessages() or use runWithContext() for context-aware error reporting.
Option 1: additionalErrorMessages()
This method lets you return more details when a test fails—like the current state of the object under test:
class ExampleTest : public el::UnitTest {
public:
    Example example;
    auto additionalErrorMessages() -> std::string override {
        try {
            return std::format("example.state = {}", example.state);
        } catch (...) {
            return "Exception in additionalErrorMessages()";
        }
    }
    void testConstruction() {
        example = Example{};
        REQUIRE(example.empty());
    }
};
This message is shown only if a test fails.
Option 2: runWithContext(...)
Use this when testing multiple cases in a loop, to show which input failed:
class ExampleTest : public el::UnitTest {
public:
    void testConstruction() {
        struct TestData {
            int value;
            std::string expectedOutput;
        };
        const auto testData = std::vector<TestData>{
            {-1, "bad"},
            {0, "good"},
            {1, "good"},
            // ...
        };
        for (const auto &data : testData) {
            runWithContext(SOURCE_LOCATION(), [&]() {
                auto ex = Example(data.value);
                std::string output;
                REQUIRE_NOTHROW(output = ex.toString());
                REQUIRE_EQUAL(output, data.expectedOutput);
            }, [&]() -> std::string {
                return std::format("Failed for value = {}", data.value);
            });
        }
    }
};