Skip to content

Catch2

Catch2 is a C++ unit-testing framework with CTest and CMake integration.

Install

sudo apt install catch2

Demo

1
2
3
4
5
6
7
8
calculator/
├── CMakeLists.txt
├── include/
   └── calculator.hpp
├── src/
   └── calculator.cpp
└── tests/
    └── test_calculator.cpp
include/calculator.hpp
1
2
3
4
#pragma once

int add(int a, int b);
int divide(int a, int b);
src/calculator.cpp
#include "calculator.hpp"

#include <stdexcept>

int add(int a, int b)
{
    return a + b;
}

int divide(int a, int b)
{
    if (b == 0)
        throw std::invalid_argument("division by zero");

    return a / b;
}
tests/test_calculator.cpp
#include <catch2/catch_test_macros.hpp>

#include "calculator.hpp"

#include <stdexcept>

TEST_CASE("add two numbers")
{
    REQUIRE(add(2, 3) == 5);
    REQUIRE(add(-1, 1) == 0);
}

TEST_CASE("divide two numbers")
{
    REQUIRE(divide(10, 2) == 5);
}

TEST_CASE("division by zero throws")
{
    REQUIRE_THROWS_AS(
        divide(10, 0),
        std::invalid_argument
    );
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.20)

project(calculator LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

include(CTest)

# -------------------------
# Application/library code
# -------------------------

add_library(calculator
    src/calculator.cpp
)

target_include_directories(calculator
    PUBLIC
        include
)

# -------------------------
# Tests
# -------------------------

if(BUILD_TESTING)
    find_package(Catch2 3 REQUIRED)

    add_executable(calculator_tests
        tests/test_calculator.cpp
    )

    target_link_libraries(calculator_tests
        PRIVATE
            calculator
            Catch2::Catch2WithMain
    )

    include(Catch)
    catch_discover_tests(calculator_tests)
endif()

Build and usage

cmake -S code -B code/build
cmake --build code/build
run tests
./code/build/calculator_tests
run test using ctest
ctest --test-dir code/build --output-on-failure

Expected result:

100% tests passed, 0 tests failed out of 3

Running calculator_tests directly should finish with this summary:

All tests passed (4 assertions in 3 test cases)

Basic Catch2 API

TEST_CASE

TEST_CASE defines an independently runnable test. Give it a description that states the behavior being verified. An optional tag, such as [calculator], can be used to select related tests from the command line.

1
2
3
4
TEST_CASE("addition returns the sum", "[calculator]")
{
    REQUIRE(add(2, 3) == 5);
}

CHECK and REQUIRE

Both macros verify a condition, but they handle failure differently:

Macro Failure behavior
CHECK(expression) Records the failure and continues the current test case.
REQUIRE(expression) Records the failure and stops the current test case immediately.

CHECK

1
2
3
4
5
TEST_CASE("example")
{
    CHECK(1 == 2);     // fails, but continues
    CHECK(2 == 3);     // this still runs
}

The example intentionally fails to demonstrate that the second CHECK still runs. It is not included in the buildable companion project.

REQUIRE

1
2
3
4
5
TEST_CASE("example")
{
    REQUIRE(1 == 2);   // fails and stops this test case
    REQUIRE(2 == 3);   // never reached
}

This example also intentionally fails. The second assertion is not evaluated, so it is kept out of the passing companion test suite.

Exception assertions

Macro Verifies
REQUIRE_THROWS(expression) The expression throws any exception.
REQUIRE_THROWS_AS(expression, type) The expression throws the expected exception type.
REQUIRE_NOTHROW(expression) The expression completes without throwing.

Use REQUIRE_THROWS_AS when the exception type is part of the behavior being tested:

1
2
3
4
5
6
7
TEST_CASE("division by zero throws")
{
    REQUIRE_THROWS_AS(
        divide(10, 0),
        std::invalid_argument
    );
}

This assertion fails if divide(10, 0) does not throw or if the thrown exception does not match std::invalid_argument. The same passing test is included in code/tests/test_calculator.cpp.


More Advance API