How Auto-Registration Works with TEST_CASE Macros in Catch2: Inside the AutoReg Mechanism
Catch2 automatically registers test cases at program startup by expanding the TEST_CASE macro into a static function and a static AutoReg object that invokes the test registry during the global initialization phase, before main() executes.
Catch2 eliminates manual test registration by leveraging the C++ preprocessor and static initialization. When you write a TEST_CASE macro in your source file, the framework automatically inserts your test into the global registry without requiring explicit registration calls. This auto-registration mechanism relies on a clever combination of macro expansion and a helper class named AutoReg that executes during the global-construction phase.
Macro Expansion: From TEST_CASE to INTERNAL_CATCH_TESTCASE2
The TEST_CASE macro defined in catch_test_macros.hpp expands through a chain of internal macros that ultimately invoke INTERNAL_CATCH_TESTCASE2. This internal macro performs two critical actions: it creates a static function to hold your test body, and it declares a static instance of AutoReg in an anonymous namespace.
When you write:
TEST_CASE( "addition works", "[math]" ) {
REQUIRE( 1 + 1 == 2 );
}
The preprocessor expands this into code resembling:
static void INTERNAL_CATCH_UNIQUE_NAME();
namespace {
const Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME_PLACEHOLDER(
Catch::makeTestInvoker(&INTERNAL_CATCH_UNIQUE_NAME),
CATCH_INTERNAL_LINEINFO,
Catch::StringRef(),
Catch::NameAndTags{ "addition works", "[math]" } );
}
static void INTERNAL_CATCH_UNIQUE_NAME() {
REQUIRE( 1 + 1 == 2 );
}
The relevant macro definitions live in src/catch2/internal/catch_test_registry.hpp (lines 8-15), while the public-facing wrappers reside in src/catch2/catch_test_macros.hpp.
The AutoReg Constructor and Static Initialization
The AutoReg class serves as the registration agent. Its constructor, declared in catch_test_registry.hpp at lines 87-89, accepts four parameters: the invoker (a function pointer wrapper), source line information, a class name (empty for free functions), and the NameAndTags structure containing your test description and tag list.
During construction, AutoReg immediately contacts the central test registry. The constructor calls Catch::getMutableRegistry().registerTest(), passing all captured metadata. Because the AutoReg instance declared by the macro has static storage duration, this constructor invocation happens during the global initialization phase— guaranteeing every test is registered before main() begins execution.
Why Static Initialization Guarantees Registration
The auto-registration pattern relies on two fundamental C++ guarantees:
-
Static objects initialize exactly once per translation unit. The
AutoReginstance created by the macro expansion ensures the registration callback executes precisely one time, eliminating duplicate registration risks. -
Anonymous namespace scoping prevents name clashes. The
AutoRegobject lives insidenamespace { }, giving it internal linkage while remaining reachable to the linker. This allows multiple translation units to defineTEST_CASEmacros without symbol collisions, yet still permits the constructor to run during program startup.
Supporting Variants: TEST_CASE_METHOD and SCENARIO
The same auto-registration pattern applies to all test-defining macros. TEST_CASE_METHOD, SCENARIO, and REGISTER_TEST_CASE all utilize AutoReg with slight parameter variations:
TEST_CASE_METHODpasses the fixture class name as the third parameter toAutoReg, allowing the framework to instantiate the fixture before invoking the test method.SCENARIOis an alias that forwards toTEST_CASEwith specific tagging conventions for behavior-driven development.REGISTER_TEST_CASEexplicitly registers a user-defined function rather than a lambda or method body.
These variants are defined in catch_test_registry.hpp (lines 55-73) and catch_test_macros.hpp (lines 151-159), all utilizing the same INTERNAL_CATCH_TESTCASE expansion strategy.
Complete Code Examples
The following examples demonstrate how different macro styles all resolve to the same static AutoReg pattern:
// Free-function test case
TEST_CASE( "vector sizing", "[container]" ) {
std::vector<int> v( 5 );
REQUIRE( v.size() == 5 );
}
// Fixture-based test using TEST_CASE_METHOD
class DatabaseFixture {
public:
DatabaseFixture() { /* setup connection */ }
~DatabaseFixture() { /* teardown */ }
bool isConnected = true;
};
TEST_CASE_METHOD( DatabaseFixture, "connection check", "[database]" ) {
REQUIRE( isConnected );
}
// Explicit registration of an existing function
void legacy_test_function() {
CHECK( 2 + 2 == 4 );
}
REGISTER_TEST_CASE( legacy_test_function, "legacy validation", "[math]" );
Each macro expands to instantiate AutoReg with the appropriate invoker type, ensuring all tests appear in the registry regardless of definition style.
Summary
- Macro expansion generates a static function and a static
AutoReginstance for everyTEST_CASE,TEST_CASE_METHOD, orSCENARIOmacro. AutoRegis a helper class defined incatch_test_registry.hppthat captures test metadata and invokes the registration API during construction.- Static initialization guarantees registration occurs before
main()runs, utilizing C++'s global construction order. - Anonymous namespaces prevent symbol collisions across translation units while maintaining the linker's ability to execute constructors.
- All variants use the same underlying mechanism, differing only in how they construct the test invoker (free function, method, or explicit function pointer).
Frequently Asked Questions
How does Catch2 register tests without explicit registration calls?
Catch2 leverages static initialization side effects. The TEST_CASE macro expands to create a static AutoReg object whose constructor automatically calls Catch::getMutableRegistry().registerTest(). Because static objects are constructed before main() executes, the framework populates its test registry without any manual intervention from the developer.
What is the AutoReg class in Catch2?
AutoReg (automatic registration) is a small helper class defined in src/catch2/internal/catch_test_registry.hpp. It acts as a bridge between the macro-generated code and Catch2's internal test registry. Its constructor accepts an invoker (wrapped function pointer), source location data, and test metadata, immediately forwarding these to the registry upon instantiation.
When exactly does test registration happen?
Registration occurs during the static initialization phase of program startup, which happens after the runtime loads your binary but before main() receives control. The C++ standard guarantees that static objects with namespace scope are initialized before the program begins executing, ensuring all TEST_CASE macros are known to the framework when the test runner starts.
How do TEST_CASE_METHOD and SCENARIO use the same mechanism?
Both macros expand to the same INTERNAL_CATCH_TESTCASE infrastructure used by TEST_CASE. TEST_CASE_METHOD additionally passes the fixture class name to AutoReg, which the framework uses to instantiate the fixture before invoking the test method. SCENARIO simply provides a semantic alias that forwards to TEST_CASE with specific default tags, utilizing identical auto-registration logic.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →