Skip to content
C++

Which C++ test framework should I use?

Decision guide for choosing between Google Test, Catch2, doctest, Boost.Test, and other C++ testing frameworks.

Start here

What matters most to you?

  1. Compile-time speed (large projects) → doctest
  2. Widest industry adoption / mocking built in → Google Test + Google Mock
  3. Readable BDD-style syntax / self-contained → Catch2
  4. Embedded or resource-constrained → doctest or a minimal framework
  5. Already using Boost → Boost.Test

Decision tree

Does compile time matter a lot?

Yes — large test suite, slow rebuilds are painful:

  • doctest — header-only, compiles 20-50× faster than Catch2 in large suites
    • Drop-in compatible with Catch2 API for basic usage
    • Preferred for projects with hundreds of test files

No / don't care:

  • Continue below

Do you need mocking built in?

Yes — want to mock interfaces, verify call counts, set expectations:

  • Google Test + Google Mock (gtest/gmock) — industry standard for mocking
    • Available as a single find_package(GTest) in CMake
    • Works well with vcpkg: vcpkg install gtest

No — mocking not required (or using a separate mock library):

  • Any framework works; choose by syntax preference

Do you prefer BDD / specification style?

Yes — prefer GIVEN / WHEN / THEN or SECTION nesting:

  • Catch2 — SCENARIO, GIVEN, WHEN, THEN, SECTION, natural assertions
  • Boost.Test — also supports BDD fixtures

No — traditional TEST / ASSERT style:

  • Google Test — TEST_F, ASSERT_EQ, EXPECT_THAT with matchers
  • doctest — very similar to Catch2 but faster to compile

Is the framework a single-header include?

Want truly zero-dependency, just drop in a file:

  • doctest — single header, no external config
  • Catch2 v2 — single header (v3 is multi-file)

Fine with cmake / package manager integration:

  • Any of the above

Quick comparison

Google TestCatch2 v3doctestBoost.Test
StylexUnitBDD + xUnitBDD + xUnitxUnit + BDD
MockingGMock (built-in)Trompeloeil (external)ExternalExternal
Compile speedMediumSlowFastSlow
Header-onlyNoNo (v3)YesNo
MatchersEXPECT_THATREQUIRE_THATLimitedBoost.Test
IDE supportExcellentGoodGoodOK
Parameterized testsYesYesYesYes
AdoptionVery highHighMediumMedium

Setup (CMake)

cmake
# Google Test (via FetchContent)
include(FetchContent)
FetchContent_Declare(googletest
    URL https://github.com/google/googletest/archive/refs/heads/main.zip)
FetchContent_MakeAvailable(googletest)
target_link_libraries(my_tests GTest::gtest_main GTest::gmock)

# Catch2 (via FetchContent)
FetchContent_Declare(Catch2
    GIT_REPOSITORY https://github.com/catchorg/Catch2.git
    GIT_TAG v3.5.0)
FetchContent_MakeAvailable(Catch2)
target_link_libraries(my_tests Catch2::Catch2WithMain)

Recommendation by scenario

ScenarioRecommended
New project, no strong preferenceCatch2 or Google Test
Large project, compile time mattersdoctest
Need mockingGoogle Test + GMock
Already using BoostBoost.Test
Embedded / no allocatordoctest (configurable)
Open-source library (contributors expect)Google Test or Catch2

Google Test — syntax reference

cpp
#include <gtest/gtest.h>

// Basic test
TEST(MathTest, AddsTwoPositives) {
    EXPECT_EQ(add(2, 3), 5);      // non-fatal — test continues on failure
    ASSERT_EQ(add(0, 0), 0);      // fatal — stops this test immediately on failure
    EXPECT_NEAR(1.0 / 3.0, 0.333, 0.001);
    EXPECT_TRUE(is_prime(7));
    EXPECT_THAT(v, ::testing::ElementsAre(1, 2, 3));  // matcher
}

// Fixture — shared setup/teardown for a group of tests
class DbTest : public ::testing::Test {
protected:
    void SetUp() override    { db.open(":memory:"); }
    void TearDown() override { db.close(); }
    Database db;
};

TEST_F(DbTest, InsertsRow) {
    db.insert({.id = 1, .name = "Alice"});
    EXPECT_EQ(db.count(), 1);
}

TEST_F(DbTest, FindsByName) {
    db.insert({.id = 1, .name = "Alice"});
    auto row = db.find("Alice");
    ASSERT_TRUE(row.has_value());
    EXPECT_EQ(row->id, 1);
}

// Parameterized test — runs the same test over multiple inputs
class PrimeTest : public ::testing::TestWithParam<int> {};

TEST_P(PrimeTest, IsPrime) {
    EXPECT_TRUE(is_prime(GetParam()));
}

INSTANTIATE_TEST_SUITE_P(KnownPrimes, PrimeTest,
    ::testing::Values(2, 3, 5, 7, 11, 13));

Google Mock — mocking interfaces

cpp
#include <gmock/gmock.h>

struct ILogger {
    virtual void log(std::string_view msg) = 0;
    virtual ~ILogger() = default;
};

class MockLogger : public ILogger {
public:
    MOCK_METHOD(void, log, (std::string_view msg), (override));
};

TEST(ServiceTest, LogsOnError) {
    MockLogger mock;
    // Expect log() to be called exactly once with any string containing "error"
    EXPECT_CALL(mock, log(::testing::HasSubstr("error")))
        .Times(1);

    Service svc{&mock};
    svc.trigger_error();
}

// ON_CALL sets default behavior; EXPECT_CALL sets behavior + expectation
TEST(ServiceTest, ReturnsData) {
    MockLogger mock;
    ON_CALL(mock, log(::testing::_)).WillByDefault(::testing::Return());
    // ... test svc behavior without caring about log calls ...
}

Catch2 — syntax reference

cpp
#define CATCH_CONFIG_MAIN
#include <catch2/catch_all.hpp>

TEST_CASE("String operations", "[string]") {
    std::string s = "hello world";

    REQUIRE(s.size() == 11);         // fatal — like ASSERT_EQ
    CHECK(s.find("world") != std::string::npos);  // non-fatal

    SECTION("uppercasing") {
        to_upper(s);
        REQUIRE(s == "HELLO WORLD");
    }

    SECTION("splitting on space") {
        auto parts = split(s, ' ');
        REQUIRE(parts.size() == 2);
        CHECK(parts[0] == "hello");
        CHECK(parts[1] == "world");
    }
}

// BDD style
SCENARIO("user logs in", "[auth]") {
    GIVEN("a valid user exists") {
        UserDb db;
        db.add_user("alice", "secret");

        WHEN("correct credentials are provided") {
            auto result = db.authenticate("alice", "secret");
            THEN("authentication succeeds") {
                REQUIRE(result == AuthResult::ok);
            }
        }

        WHEN("wrong password is provided") {
            auto result = db.authenticate("alice", "wrong");
            THEN("authentication fails") {
                REQUIRE(result == AuthResult::bad_password);
            }
        }
    }
}

CMake + CTest integration

cmake
include(CTest)
enable_testing()

add_executable(myapp_tests tests/math_test.cpp tests/db_test.cpp)
target_link_libraries(myapp_tests PRIVATE myapp_lib GTest::gtest_main GTest::gmock)

# Register with CTest — run with: ctest --test-dir build
include(GoogleTest)
gtest_discover_tests(myapp_tests)
bash
# Run all tests:
ctest --test-dir build --output-on-failure -j$(nproc)

# Run only tests matching a pattern:
ctest --test-dir build -R "DbTest" --output-on-failure

# Run with Google Test filter directly:
./build/myapp_tests --gtest_filter="DbTest.*"

google-benchmark — microbenchmarks

When tests pass but you need to verify performance, use google-benchmark alongside your test suite:

cpp
#include <benchmark/benchmark.h>
#include <vector>
#include <algorithm>

static void BM_SortVector(benchmark::State& state) {
    std::vector<int> data(state.range(0));
    std::iota(data.begin(), data.end(), 0);
    std::shuffle(data.begin(), data.end(), std::mt19937{42});

    for (auto _ : state) {
        auto copy = data;
        std::sort(copy.begin(), copy.end());
        benchmark::DoNotOptimize(copy.data());  // prevent elision
        benchmark::ClobberMemory();
    }
    state.SetItemsProcessed(state.iterations() * state.range(0));
}

// Run with N = 1024, 4096, 16384
BENCHMARK(BM_SortVector)->Range(1024, 1 << 14);

// Compare two implementations
static void BM_LinearSearch(benchmark::State& state) {
    std::vector<int> v(1000);
    std::iota(v.begin(), v.end(), 0);
    for (auto _ : state) {
        benchmark::DoNotOptimize(std::find(v.begin(), v.end(), 999));
    }
}
BENCHMARK(BM_LinearSearch);

BENCHMARK_MAIN();
cmake
find_package(benchmark REQUIRED)
add_executable(perf_tests bench/sort_bench.cpp)
target_link_libraries(perf_tests PRIVATE benchmark::benchmark_main)
bash
./perf_tests --benchmark_format=json > results.json
./perf_tests --benchmark_filter="BM_Sort"
./perf_tests --benchmark_repetitions=5 --benchmark_report_aggregates_only=true