# How Auto-Registration Works with TEST_CASE Macros in Catch2: Inside the AutoReg Mechanism

> Discover how Catch2 automatically registers TEST_CASE macros at startup. Learn about the AutoReg mechanism and global initialization phase before main executes.

- Repository: [Catch Org/Catch2](https://github.com/catchorg/Catch2)
- Tags: internals
- Published: 2026-07-29

---

**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`](https://github.com/catchorg/Catch2/blob/main/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:

```cpp
TEST_CASE( "addition works", "[math]" ) {
    REQUIRE( 1 + 1 == 2 );
}

```

The preprocessor expands this into code resembling:

```cpp
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`](https://github.com/catchorg/Catch2/blob/main/src/catch2/internal/catch_test_registry.hpp) (lines 8-15), while the public-facing wrappers reside in [`src/catch2/catch_test_macros.hpp`](https://github.com/catchorg/Catch2/blob/main/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`](https://github.com/catchorg/Catch2/blob/main/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 `AutoReg` instance created by the macro expansion ensures the registration callback executes precisely one time, eliminating duplicate registration risks.

- **Anonymous namespace scoping** prevents name clashes. The `AutoReg` object lives inside `namespace { }`, giving it internal linkage while remaining reachable to the linker. This allows multiple translation units to define `TEST_CASE` macros 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_METHOD`** passes the fixture class name as the third parameter to `AutoReg`, allowing the framework to instantiate the fixture before invoking the test method.
- **`SCENARIO`** is an alias that forwards to `TEST_CASE` with specific tagging conventions for behavior-driven development.
- **`REGISTER_TEST_CASE`** explicitly registers a user-defined function rather than a lambda or method body.

These variants are defined in [`catch_test_registry.hpp`](https://github.com/catchorg/Catch2/blob/main/catch_test_registry.hpp) (lines 55-73) and [`catch_test_macros.hpp`](https://github.com/catchorg/Catch2/blob/main/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:

```cpp
// 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 `AutoReg` instance for every `TEST_CASE`, `TEST_CASE_METHOD`, or `SCENARIO` macro.
- **`AutoReg`** is a helper class defined in [`catch_test_registry.hpp`](https://github.com/catchorg/Catch2/blob/main/catch_test_registry.hpp) that 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`](https://github.com/catchorg/Catch2/blob/main/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.