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?
- Compile-time speed (large projects) → doctest
- Widest industry adoption / mocking built in → Google Test + Google Mock
- Readable BDD-style syntax / self-contained → Catch2
- Embedded or resource-constrained → doctest or a minimal framework
- 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
- Available as a single
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_THATwith 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 Test | Catch2 v3 | doctest | Boost.Test | |
|---|---|---|---|---|
| Style | xUnit | BDD + xUnit | BDD + xUnit | xUnit + BDD |
| Mocking | GMock (built-in) | Trompeloeil (external) | External | External |
| Compile speed | Medium | Slow | Fast | Slow |
| Header-only | No | No (v3) | Yes | No |
| Matchers | EXPECT_THAT | REQUIRE_THAT | Limited | Boost.Test |
| IDE support | Excellent | Good | Good | OK |
| Parameterized tests | Yes | Yes | Yes | Yes |
| Adoption | Very high | High | Medium | Medium |
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
| Scenario | Recommended |
|---|---|
| New project, no strong preference | Catch2 or Google Test |
| Large project, compile time matters | doctest |
| Need mocking | Google Test + GMock |
| Already using Boost | Boost.Test |
| Embedded / no allocator | doctest (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