How to Use a Custom Main Function with Catch2: Four Implementation Patterns

To use a custom main function with Catch2, include <catch2/catch_session.hpp> and instantiate Catch::Session to manually control test execution, configuration, and command-line parsing instead of linking against the default Catch2Main library.

While the catchorg/Catch2 framework provides a default main() function via the Catch2Main library, many projects require custom initialization, teardown, or specialized command-line handling. By using the Catch::Session class defined in src/catch2/catch_session.hpp, you can replace the default entry point while retaining full access to Catch2's test runner and configuration system.

Understanding the Catch::Session API

The Catch::Session class serves as the primary interface for custom main implementations. According to the Catch2 source code, this class encapsulates the configuration state (ConfigData), the Clara-based command-line parser, and the run() method that executes the test suite. A single Session instance must exist throughout the application lifetime, managing the transition from argument parsing to test execution through methods like applyCommandLine() and runInternal().

Key methods exposed in src/catch2/catch_session.hpp include:

  • run(int argc, char const* argv[]) - Single-step initialization and execution
  • applyCommandLine(int argc, char const* argv[]) - Parses arguments without running tests
  • configData() - Returns a mutable reference to the underlying ConfigData structure
  • cli() - Gets or sets the Clara command-line parser instance
  • useConfigData(ConfigData const&) - Bypasses CLI parsing entirely

Basic Custom Main Implementation

The simplest pattern forwards command-line arguments directly to Catch2 while wrapping the test run with custom setup and teardown logic.

#include <catch2/catch_session.hpp>

int main(int argc, char* argv[]) {
    // Optional pre-test setup
    int result = Catch::Session().run(argc, argv);
    // Optional post-test cleanup
    return result;
}

This creates a temporary Session object and invokes run(int, char const*[]) (lines 30-53 in catch_session.hpp), which internally calls applyCommandLine() followed by runInternal(). Use this approach when you need to execute code before tests start or after they complete, but do not need to modify Catch2's configuration.

Modifying Configuration After Parsing

For scenarios requiring programmatic configuration adjustments after CLI parsing, instantiate a persistent Session object and manipulate its ConfigData before calling run().

#include <catch2/catch_session.hpp>

int main(int argc, char* argv[]) {
    Catch::Session session;  // Exactly one instance required
    
    // Set defaults before CLI parsing
    session.configData().verbosity = Catch::Verbosity::High;
    
    int rc = session.applyCommandLine(argc, argv);
    if (rc != 0) return rc;  // Handle CLI errors
    
    // Override configuration after parsing
    session.configData().abortAfter = 5;  // Stop after 5 failures
    
    return session.run();
}

The applyCommandLine() method (line 36) populates the internal ConfigData structure from argc/argv. After this returns successfully, you can modify values through configData() (accessor at line 58) or replace the entire configuration object to customize test behavior without changing command-line arguments.

Adding Custom Command-Line Options

To extend Catch2's built-in argument parser with project-specific options, access the Clara parser via cli() and compose additional flags using the pipe operator.

#include <catch2/catch_session.hpp>
#include <iostream>

int main(int argc, char* argv[]) {
    Catch::Session session;
    int height = 0;  // User-defined variable
    
    using namespace Catch::Clara;
    auto cli = session.cli() |
               Opt(height, "height")
               ["-g"]["--height"]
               ("How high?");
    
    session.cli(cli);  // Replace parser with extended version
    
    int rc = session.applyCommandLine(argc, argv);
    if (rc != 0) return rc;
    
    if (height > 0)
        std::cout << "height: " << height << '\n';
    
    return session.run();
}

The Session class exposes its Clara parser via cli() const (line 55) and accepts modifications through cli(Clara::Parser const&) (line 56). By OR-ing (|) a new Opt object, you extend the command line without invalidating Catch2's core parser functionality.

Bypassing Command-Line Parsing Entirely

For embedded environments or fixed test configurations where command-line arguments are unavailable, construct a ConfigData object programmatically and inject it directly into the session.

#include <catch2/catch_session.hpp>

int main() {
    Catch::Session session;
    
    // Build configuration manually
    Catch::ConfigData cfg;
    cfg.shouldThrow = false;
    cfg.verbosity = Catch::Verbosity::Quiet;
    
    session.useConfigData(cfg);  // Bypass CLI completely
    return session.run();
}

Calling useConfigData() (line 41) replaces the automatically built configuration, allowing you to skip applyCommandLine() entirely. This pattern is documented in docs/own-main.md and is particularly useful when integrating Catch2 into applications with constrained execution environments.

Summary

  • Include <catch2/catch_session.hpp> (or extras/catch_amalgamated.hpp for single-header builds) to access the Catch::Session class
  • Link against the Catch2 target instead of Catch2Main when providing your own main() function
  • Use session.applyCommandLine() to parse arguments, then modify session.configData() for post-parse configuration changes
  • Extend the CLI parser by composing Catch::Clara::Opt objects with session.cli()
  • Call session.useConfigData() to bypass command-line parsing entirely for fixed configurations

Frequently Asked Questions

No. When providing your own main() function, link against the Catch2 static library target instead of Catch2Main. The Catch2Main library exists solely to supply the default entry point, while Catch2 contains the test framework and Session class without a predefined main().

Can I use the amalgamated header with a custom main implementation?

Yes. The extras/catch_amalgamated.hpp file contains the complete Catch::Session implementation and all related configuration classes. Include this single header instead of the modular headers when using the amalgamated distribution of Catch2.

How do I handle command-line parsing errors in a custom main?

Check the integer return value from session.applyCommandLine(argc, argv). If this method returns non-zero, it indicates a CLI parsing error (such as unknown arguments), and you should return this value immediately to propagate the error to the shell without attempting to run tests.

Is it safe to modify ConfigData after calling applyCommandLine?

Yes. You can modify the configuration via session.configData() after applyCommandLine() returns successfully to override user-provided defaults programmatically. Alternatively, use session.useConfigData() to replace the entire configuration object before calling run().

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →