Written and maintained by CASRAI Editorial Board
Published
On this page
Catch2 (C++ Automated Test Cases in Headers) is a modern, expressive unit testing, Test-Driven Development (TDD), and Behavior-Driven Development (BDD) framework for C++14, C++17, and C++20. Designed to minimize boilerplate, Catch2 decomposes standard C++ expressions directly inside test assertions, eliminating the need for specialized comparison macros. For scientific computing teams and research software engineers (RSEs), Catch2 provides seamless build-system integration via CMake, native floating-point tolerance matchers for numerical simulations, and automated test discovery in continuous integration pipelines.
What it is used for in research
In computational research, software correctness directly underpins scientific validity. Whether developing climate models, molecular dynamics engines, astrophysics simulations, or bioinformatics pipelines, unhandled numerical divergence or regressions compromise published findings. Catch2 serves as a core quality assurance foundation across computational research workflows:
- Scientific Simulation Verification: Research software frequently solves differential equations, linear algebra systems, and Monte Carlo stochastic algorithms. Catch2 provides dedicated floating-point matchers (such as relative and absolute margin comparisons) that allow researchers to verify numerical convergence without falling prey to IEEE 754 precision representation errors. This supports robust numerical accuracy before submitting manuscripts or code to the Journal of Open Source Software (JOSS).
- Regression Testing in Long-Lived Codebases: Academic software often evolves across successive cohorts of doctoral researchers and postdocs. Catch2 enables continuous integration (CI) test suites that automatically flag unintended side effects when optimizing performance-critical algorithms or refactoring legacy C++ routines, complementing formal verification techniques.
- Modular Test Isolation via Hierarchical Sections: Scientific experiments often require initializing complex test fixtures, such as grid meshes or particle coordinate tables. Catch2 test cases support local nested
SECTIONblocks where each section executes independently from the top of the enclosing test case, eliminating the brittle setup and teardown classes mandated by older testing frameworks. - Peer Review and Reproducibility Audits: Modern scientific publishing guidelines increasingly mandate adherence to the FAIR4RS principles for research software. Distributing readable, automated unit tests alongside published repositories enables reviewers to verify software correctness and guarantees computational reproducibility across different operating systems and compiler toolchains.
- Integration with Research Software Review: Catch2 generates standardized machine-readable test outputs (such as JUnit XML and SonarQube reports) that streamline automated pull-request validation during code review for research software projects.
Catch2 is distributed under the Boost Software License 1.0, an OSI-approved permissive license that facilitates adoption in both open-source and proprietary academic research partnerships, fully compliant with modern open-source software development practices.
Install or access
Starting with version 3 (Catch2 v3), the framework transitioned from a monolithic single-header file to a statically compiled library with modular headers. This transition substantially accelerates incremental build times in multi-file scientific projects. Catch2 can be incorporated into research projects through several official mechanisms.
Method 1: CMake FetchContent (Recommended)
For reproducible computational pipelines, using CMake’s built-in FetchContent module is the standard approach. It ensures all collaborators and CI build nodes download the exact specified release without requiring manual system package installations. Add the following block to your CMakeLists.txt:
cmake_minimum_required(VERSION 3.16)
project(ResearchSimulation CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
include(FetchContent)
FetchContent_Declare(
Catch2
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
GIT_TAG v3.5.2
)
FetchContent_MakeAvailable(Catch2)
Method 2: Package Managers (vcpkg and Conan)
For scientific projects managing complex multi-library dependency trees (such as Boost, Eigen, and HDF5), Catch2 is officially packaged in C++ dependency managers:
- vcpkg: Run
vcpkg install catch2, and linkfind_package(Catch2 3 REQUIRED)in your build configuration. - Conan: Add
catch2/3.5.2to yourconanfile.txtdependencies.
Method 3: Linux System Package Repositories
On Debian, Ubuntu, and Fedora HPC clusters or workstations, system-wide versions can be installed via standard package managers:
# Ubuntu / Debian
sudo apt update && sudo apt install catch2
# Fedora / Red Hat Enterprise Linux
sudo dnf install catch2-devel
Step-by-step task
This tutorial walks through implementing an automated unit testing suite for a numerical research calculation (a Lennard-Jones interatomic potential calculator) using Catch2 v3, CMake, and CTest.
Step 1: Structure the Project Directory
Organize your repository to separate core numerical algorithms from test suites:
simulation_project/
├── CMakeLists.txt
├── include/
│ └── potential.hpp
├── src/
│ └── potential.cpp
└── tests/
├── CMakeLists.txt
└── test_potential.cpp
Step 2: Define the Scientific Function
In include/potential.hpp, define a simple function to compute the Lennard-Jones 12-6 potential: V(r) = 4ε × [(σ/r)¹² − (σ/r)⁶].
#pragma once
#include <cmath>
#include <stdexcept>
inline double lennard_jones_potential(double r, double epsilon, double sigma) {
if (r <= 0.0) {
throw std::invalid_argument("Interatomic distance r must be positive.");
}
double term = sigma / r;
double term6 = std::pow(term, 6);
double term12 = term6 * term6;
return 4.0 * epsilon * (term12 - term6);
}
Step 3: Author Catch2 Unit Tests with Floating-Point Matchers
Create tests/test_potential.cpp. In Catch2 v3, core assertions are imported via <catch2/catch_test_macros.hpp>, and floating-point comparisons require <catch2/matchers/catch_matchers_floating_point.hpp>:
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_floating_point.hpp>
#include "potential.hpp"
TEST_CASE("Lennard-Jones potential evaluations", "[physics][numerical]") {
const double epsilon = 1.0;
const double sigma = 1.0;
SECTION("Potential crosses zero at r = sigma") {
double v = lennard_jones_potential(1.0, epsilon, sigma);
REQUIRE_THAT(v, Catch::Matchers::WithinAbs(0.0, 1e-12));
}
SECTION("Potential reaches minimum at r = 2^(1/6) * sigma") {
double r_min = std::pow(2.0, 1.0 / 6.0) * sigma;
double v_min = lennard_jones_potential(r_min, epsilon, sigma);
// Minimum depth is exactly -epsilon
REQUIRE_THAT(v_min, Catch::Matchers::WithinRel(-1.0, 1e-7));
}
SECTION("Potential approaches repulsive core at short distances") {
double v_short = lennard_jones_potential(0.8, epsilon, sigma);
CHECK(v_short > 0.0);
}
SECTION("Zero or negative distance throws invalid_argument") {
REQUIRE_THROWS_AS(lennard_jones_potential(0.0, epsilon, sigma), std::invalid_argument);
REQUIRE_THROWS_AS(lennard_jones_potential(-1.5, epsilon, sigma), std::invalid_argument);
}
}
Step 4: Configure CMake and CTest Registration
In tests/CMakeLists.txt, link against Catch2::Catch2WithMain to automatically provide a standard test runner entry point without writing a custom main() function. Then call catch_discover_tests for automated CTest registration:
add_executable(run_unit_tests test_potential.cpp)
target_include_directories(run_unit_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../include)
# Catch2::Catch2WithMain provides the entry point main()
target_link_libraries(run_unit_tests PRIVATE Catch2::Catch2WithMain)
include(CTest)
include(Catch)
catch_discover_tests(run_unit_tests)
Step 5: Compile and Execute the Test Suite
Build the test binary using CMake from your terminal:
cmake -B build -S . -DCMAKE_BUILD_TYPE=Release
cmake --build build --parallel
Execute the compiled test binary directly, or run the suite via CTest:
# Direct execution with verbose success output
./build/tests/run_unit_tests -s
# Automated CTest execution
ctest --test-dir build --output-on-failure
Output and how to read it
Catch2 provides readable terminal formatting designed to pinpoint failure locations and inspect exact values evaluated during runtime.
Standard Console Output for Passing Tests
When running with the -s (or --success) flag, Catch2 lists all passing assertions followed by a run summary:
===============================================================================
All tests passed (5 assertions in 1 test case)
Diagnosing Assertion Failures
When an assertion fails, Catch2 decomposes the expression, displaying the exact file, line number, captured expression, and expanded runtime values:
-------------------------------------------------------------------------------
Lennard-Jones potential evaluations
Potential reaches minimum at r = 2^(1/6) * sigma
-------------------------------------------------------------------------------
tests/test_potential.cpp:18
...............................................................................
tests/test_potential.cpp:21: FAILED:
REQUIRE_THAT( v_min, Catch::Matchers::WithinRel(-1.0, 1e-7) )
with expansion:
-0.9875412 is within 0.00001% of -1.0
===============================================================================
test cases: 1 | 1 failed
assertions: 3 | 2 passed | 1 failed
In this failure report, Catch2 clarifies that the failure occurred inside the nested SECTION("Potential reaches minimum...") at line 21, showing that v_min evaluated to -0.9875412 rather than the expected -1.0 within the specified relative margin.
Generating Machine-Readable CI Output
For automated research software pipelines, Catch2 can format test results using the -r (or --reporter) flag:
# Output JUnit XML report for CI/CD dashboards
./build/tests/run_unit_tests -r junit -o junit-report.xml
# Output SonarQube format for static analysis platforms
./build/tests/run_unit_tests -r sonarqube -o sonarqube-report.xml
Troubleshooting
Undefined Reference to main
Symptom: Linker error: undefined reference to 'main' or unresolved external symbol _main.
Root Cause: Linking against Catch2::Catch2 instead of Catch2::Catch2WithMain, or omitting #define CATCH_CONFIG_MAIN in legacy v2 setups.
Resolution: In your CMakeLists.txt, ensure the test executable links against Catch2::Catch2WithMain. Alternatively, if writing a custom runner, include <catch2/catch_session.hpp> and provide an explicit int main(int argc, char* argv[]) entry point.
Slow Incremental Compilation Times
Symptom: Recompiling a single test file causes long rebuild times across the entire test suite.
Root Cause: Including <catch2/catch_all.hpp> in multiple test translation units, forcing the compiler to parse all matchers, generators, and reporters repeatedly.
Resolution: Replace <catch2/catch_all.hpp> with modular header inclusions: use <catch2/catch_test_macros.hpp> for standard tests and only include specific headers (such as <catch2/matchers/catch_matchers_floating_point.hpp>) in the specific test files that require them.
Floating-Point Equality Failures
Symptom: REQUIRE(computed == expected) fails intermittently across different CPU architectures, compiler optimization levels (-O2 vs -O3), or math libraries.
Root Cause: Exact binary equality comparisons fail due to rounding variances and fused multiply-add (FMA) instructions in floating-point hardware.
Resolution: Never use == on floating-point variables. Use Catch2 matchers: REQUIRE_THAT(val, Catch::Matchers::WithinRel(target, 1e-6)) for relative tolerances or Catch::Matchers::WithinAbs(target, 1e-9) for values near zero.
CTest Discovers Zero Tests
Symptom: ctest reports No tests were found!!! despite test binaries building successfully.
Root Cause: catch_discover_tests() runs the test executable during CMake configuration or post-build to list test cases via the binary’s --list-test-names-only flag; if cross-compiling or dynamic shared libraries are not on the system library path (LD_LIBRARY_PATH), discovery fails silently.
Resolution: Verify the test executable runs directly from the terminal. If dynamic dependencies are missing, set set(CTEST_ENVIRONMENT "LD_LIBRARY_PATH=...") or verify shared library paths in CMake.
Free alternatives
Several free and open-source testing frameworks are available for C++ research software. The ideal choice depends on compilation speed constraints, dependency footprint, and test architecture requirements.
| Framework | License | Architecture | Primary Research Strengths | Trade-Offs & Computational Overhead |
|---|---|---|---|---|
| Catch2 | Boost Software License 1.0 (Free, permissive) | Compiled library (v3) with modular headers | Natural C++ expression decomposition, nested section fixture isolation, native floating-point matchers, CMake test discovery | Moderate compilation time in very large test suites compared to minimal single-header libraries |
| Google Test (GTest) | BSD 3-Clause (Free, open-source) | Compiled library with separate GMock mocking engine | Industry standard in massive enterprise codebases, extensive death-test and parameterization support | Requires distinct assertion macros (EXPECT_EQ, ASSERT_NEAR), verbose class-based fixtures, heavier CMake setup |
| Doctest | MIT License (Free, permissive) | Single-header or modular lightweight framework | Ultra-fast compile times, lowest runtime overhead, syntax modeled directly after Catch2 | Smaller ecosystem of specialized matchers, fewer built-in reporters, smaller community maintenance base |
| Boost.Test | Boost Software License 1.0 (Free, permissive) | Part of the Boost C++ Libraries (header-only or compiled) | Extensive integration for codebases already relying heavily on Boost libraries, rich execution monitors | Complex macro system, steep learning curve, significant header inclusion overhead if Boost is not already required |
For scientific research teams, Catch2 offers an optimal balance between expressive assertion syntax and rigorous test isolation. Teams prioritizing compilation speed in resource-constrained environments may evaluate Doctest, while projects interfacing with legacy industrial enterprise stacks typically maintain Google Test.
FAQ
What is the difference between REQUIRE and CHECK in Catch2?
Both macros evaluate standard C++ boolean expressions. However, REQUIRE halts the current test case immediately upon failure, preventing subsequent assertions or dangerous dereferences of uninitialized memory. In contrast, CHECK logs the failure details and continues executing remaining assertions within the test case, allowing researchers to observe multiple failure conditions across a single run.
How do Catch2 sections differ from traditional fixture classes?
In traditional testing frameworks like Google Test, test fixtures require defining a C++ class that inherits from ::testing::Test and overriding SetUp() and TearDown() virtual methods. Catch2 replaces this with hierarchical SECTION blocks within a single TEST_CASE. When a test case runs, Catch2 executes it from the top once for each leaf section, ensuring that local variables initialized before a section are re-initialized cleanly for every branch.
Can Catch2 execute tests matching specific tags?
Yes. Catch2 supports tagging test cases using bracketed strings in the second argument of TEST_CASE("name", "[tag1][tag2]"). From the command line, users can filter execution by passing tags directly: ./run_unit_tests [physics] runs all tests tagged with [physics], while ./run_unit_tests ~[numerical] excludes tests tagged with [numerical].
How can benchmark tests be executed in Catch2?
Catch2 provides a built-in micro-benchmarking engine via <catch2/benchmark/catch_benchmark.hpp>. By wrapping computational code in a BENCHMARK("label") { return algorithm(); }; block inside a test case, Catch2 automatically samples execution runs, measures system jitter, and outputs mean execution time and standard deviation, aiding computational complexity analysis in algorithmic research.
Sources
- Catch2 Official GitHub Repository: github.com/catchorg/Catch2 (Checked 2026-11-09).
- Catch2 Official Tutorial: github.com/catchorg/Catch2/blob/devel/docs/tutorial.md (Checked 2026-11-09).
- Catch2 CMake Integration Guide: github.com/catchorg/Catch2/blob/devel/docs/cmake-integration.md (Checked 2026-11-09).
- Catch2 Command Line Reference: github.com/catchorg/Catch2/blob/devel/docs/command-line.md (Checked 2026-11-09).
- Catch2 Built-in Reporters Reference: github.com/catchorg/Catch2/blob/devel/docs/reporters.md (Checked 2026-11-09).
- Catch2 v2 to v3 Migration Guide: github.com/catchorg/Catch2/blob/devel/docs/migrating-v2-to-v3.md (Checked 2026-11-09).
- Catch2 ReadTheDocs Documentation: catch2.readthedocs.io/en/latest (Checked 2026-11-09).







