GoogleTest
Google's C++ testing framework — xUnit-style tests, rich matchers, mocking with gMock, and seamless CMake/CTest integration.
TL;DR
GoogleTest (gtest) is the most widely used C++ unit testing framework. It provides xUnit-style TEST() macros, a rich matcher library, and gMock for mocking. Add it via CMake FetchContent or vcpkg and you're writing tests in minutes.
#include <gtest/gtest.h>
TEST(MathTest, AddsTwoIntegers) {
EXPECT_EQ(2 + 2, 4);
ASSERT_GT(10, 5);
}
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}Setup
CMake FetchContent (recommended)
include(FetchContent)
FetchContent_Declare(
googletest
URL https://github.com/google/googletest/archive/refs/tags/v1.15.2.zip
)
FetchContent_MakeAvailable(googletest)
enable_testing()
add_executable(myapp_tests tests/math_test.cpp)
target_link_libraries(myapp_tests GTest::gtest_main GTest::gmock)
include(GoogleTest)
gtest_discover_tests(myapp_tests)vcpkg
vcpkg install gtestfind_package(GTest CONFIG REQUIRED)
target_link_libraries(myapp_tests GTest::gtest_main GTest::gmock)Test macros
// Basic assertions — non-fatal (test continues)
EXPECT_EQ(actual, expected); // a == b
EXPECT_NE(actual, expected); // a != b
EXPECT_LT(actual, expected); // a < b
EXPECT_LE(actual, expected); // a <= b
EXPECT_GT(actual, expected); // a > b
EXPECT_GE(actual, expected); // a >= b
EXPECT_TRUE(condition);
EXPECT_FALSE(condition);
// Fatal assertions — test stops on failure
ASSERT_EQ(actual, expected);
ASSERT_TRUE(condition);
// ... (ASSERT_ variants for all EXPECT_ macros)
// String matchers
EXPECT_STREQ("hello", str.c_str());
EXPECT_STRCASEEQ("Hello", str.c_str()); // case-insensitive
// Floating point
EXPECT_FLOAT_EQ(3.14f, result); // ~4 ULP tolerance
EXPECT_DOUBLE_EQ(3.14, result);
EXPECT_NEAR(3.14, result, 0.001); // custom toleranceTest fixtures
class DatabaseTest : public ::testing::Test {
protected:
void SetUp() override {
db_.connect("test_db");
db_.clear();
}
void TearDown() override {
db_.disconnect();
}
Database db_;
};
TEST_F(DatabaseTest, InsertAndRetrieve) {
db_.insert("key", "value");
EXPECT_EQ(db_.get("key"), "value");
}
TEST_F(DatabaseTest, ReturnNullForMissing) {
EXPECT_EQ(db_.get("missing"), std::nullopt);
}SetUp runs before each test; TearDown after each. The fixture object is recreated for every test — no shared state.
Parameterized tests
class PrimeTest : public ::testing::TestWithParam<int> {};
TEST_P(PrimeTest, IsPrime) {
EXPECT_TRUE(isPrime(GetParam()));
}
INSTANTIATE_TEST_SUITE_P(
PrimeNumbers,
PrimeTest,
::testing::Values(2, 3, 5, 7, 11, 13)
);For multiple parameter types:
using MyTypes = ::testing::Types<int, long, float>;
TYPED_TEST_SUITE(TypedMathTest, MyTypes);
TYPED_TEST(TypedMathTest, Zero) {
TypeParam x = 0;
EXPECT_EQ(x + x, x);
}gMock — mocking
#include <gmock/gmock.h>
class MockFileSystem : public FileSystem {
public:
MOCK_METHOD(bool, exists, (const std::string& path), (override));
MOCK_METHOD(std::string, read, (const std::string& path), (override));
};
TEST(BackupTest, SkipsNonExistentFiles) {
MockFileSystem fs;
EXPECT_CALL(fs, exists("/data/file.txt")).WillOnce(::testing::Return(false));
EXPECT_CALL(fs, read(::testing::_)).Times(0); // never called
BackupService svc(fs);
svc.backup("/data/file.txt");
}Common matchers
using namespace ::testing;
EXPECT_CALL(mock, method(Eq(42))); // exact value
EXPECT_CALL(mock, method(Gt(10))); // > 10
EXPECT_CALL(mock, method(HasSubstr("foo"))); // string contains
EXPECT_CALL(mock, method(_)); // any argument
EXPECT_CALL(mock, method(An<int>())); // any int
// Return values
.WillOnce(Return(42));
.WillRepeatedly(Return(0));
.WillOnce(Throw(std::runtime_error("fail")));
.WillOnce(Invoke([](int x) { return x * 2; }));
// Call count
.Times(0); // never called
.Times(1); // exactly once
.Times(AtLeast(2)); // at least twice
.Times(Between(1,3));Exception testing
TEST(ParserTest, ThrowsOnInvalidInput) {
EXPECT_THROW(parse("!!!"), std::invalid_argument);
EXPECT_NO_THROW(parse("valid"));
}
// Check exception message
TEST(ParserTest, ExceptionMessage) {
try {
parse("!!!");
FAIL() << "Expected exception";
} catch (const std::invalid_argument& e) {
EXPECT_THAT(e.what(), HasSubstr("invalid"));
}
}Running tests
# Run all tests
./myapp_tests
# Run specific test
./myapp_tests --gtest_filter=DatabaseTest.InsertAndRetrieve
# Run tests matching pattern
./myapp_tests --gtest_filter=*Prime*
# Repeat flaky tests
./myapp_tests --gtest_repeat=100 --gtest_filter=FlakyTest*
# Output XML for CI
./myapp_tests --gtest_output=xml:results.xml
# Via CTest
ctest --test-dir build -VBest practices
Name tests clearly: TEST(ClassName, MethodName_Scenario_ExpectedResult) — e.g., TEST(Stack, Push_OnEmptyStack_SizeIsOne).
One assertion per test: Prefer multiple small tests over one large test with many assertions. A failing ASSERT stops the test, hiding other failures.
Test behavior, not implementation: Test via the public interface. Don't test private methods directly.
Keep tests fast: Mock external dependencies (DB, network, filesystem). Unit tests should run in milliseconds.
Use SCOPED_TRACE for parameterized failure messages:
for (auto& input : test_cases) {
SCOPED_TRACE("Input: " + input.name);
EXPECT_EQ(compute(input.value), input.expected);
}Test Fixtures (TEST_F) in Depth
Fixtures are the primary tool for sharing setup logic across related tests. Every TEST_F in a suite gets a fresh fixture object — SetUp runs before the test body, TearDown runs after it, even when the test fails.
#include <gtest/gtest.h>
#include "http_client.h"
class HttpClientTest : public ::testing::Test {
protected:
// Called before EACH test in this suite.
void SetUp() override {
client_ = std::make_unique<HttpClient>();
client_->setBaseUrl("http://localhost:8080");
client_->setTimeout(std::chrono::seconds(5));
}
// Called after EACH test, even if it fails or throws.
void TearDown() override {
client_->disconnect();
// No manual delete — unique_ptr cleans up automatically.
}
// Helpers visible to all TEST_F bodies in this suite.
HttpResponse get(const std::string& path) {
return client_->get(path);
}
std::unique_ptr<HttpClient> client_;
};
TEST_F(HttpClientTest, ReturnsOkForHealthEndpoint) {
auto resp = get("/health");
EXPECT_EQ(resp.statusCode, 200);
}
TEST_F(HttpClientTest, Returns404ForUnknownPath) {
auto resp = get("/no-such-route");
EXPECT_EQ(resp.statusCode, 404);
}RAII in Fixtures
Prefer RAII members over explicit TearDown. If SetUp can throw, any members already constructed will be destroyed automatically — but TearDown is not called when SetUp throws. Wrap resources in smart pointers or RAII guards so cleanup is guaranteed regardless.
class TempDirTest : public ::testing::Test {
protected:
void SetUp() override {
// TempDir constructor creates the directory; destructor removes it.
// If a later SetUp line throws, TempDir's destructor still fires.
tmpDir_ = std::make_unique<TempDir>("/tmp/gtest_XXXXXX");
}
// No TearDown needed — TempDir RAII handles removal.
std::unique_ptr<TempDir> tmpDir_;
};Suite-Level Setup with SetUpTestSuite
Use SetUpTestSuite / TearDownTestSuite for expensive one-time state (e.g., starting a server, loading a large data file). These are static methods called once per suite rather than once per test.
class ServerTest : public ::testing::Test {
public:
static void SetUpTestSuite() {
server_ = std::make_unique<TestServer>();
server_->start(9000);
}
static void TearDownTestSuite() {
server_->stop();
server_.reset();
}
protected:
static std::unique_ptr<TestServer> server_;
};
// Definition in .cpp
std::unique_ptr<TestServer> ServerTest::server_;
TEST_F(ServerTest, AcceptsConnections) {
EXPECT_TRUE(server_->isRunning());
}Parameterized Tests in Depth
Parameterized tests eliminate copy-paste by running the same logic over many inputs. TEST_P pairs with INSTANTIATE_TEST_SUITE_P to generate a named test case for every value.
#include <gtest/gtest.h>
struct DivCase { int num, den, expected; };
class DivisionTest : public ::testing::TestWithParam<DivCase> {};
TEST_P(DivisionTest, ComputesQuotient) {
auto [num, den, expected] = GetParam();
EXPECT_EQ(num / den, expected);
}
INSTANTIATE_TEST_SUITE_P(
BasicDivision,
DivisionTest,
::testing::Values(
DivCase{10, 2, 5},
DivCase{9, 3, 3},
DivCase{7, 1, 7}
)
);ValuesIn — Driving Tests from a Container
const std::vector<std::string> kValidUrls = {
"https://example.com",
"http://foo.bar/path?q=1",
};
class UrlParseTest : public ::testing::TestWithParam<std::string> {};
TEST_P(UrlParseTest, ParsesWithoutThrowing) {
EXPECT_NO_THROW(Url::parse(GetParam()));
}
INSTANTIATE_TEST_SUITE_P(ValidUrls, UrlParseTest,
::testing::ValuesIn(kValidUrls));Combine — Cartesian Product of Parameter Sets
testing::Combine generates the full Cartesian product — useful for testing every combination of two independent axes.
class EncodingTest
: public ::testing::TestWithParam<std::tuple<Encoding, CompressionLevel>> {};
TEST_P(EncodingTest, RoundTrips) {
auto [enc, level] = GetParam();
auto data = makeTestPayload();
EXPECT_EQ(decode(encode(data, enc, level), enc), data);
}
INSTANTIATE_TEST_SUITE_P(
All,
EncodingTest,
::testing::Combine(
::testing::Values(Encoding::UTF8, Encoding::UTF16, Encoding::Latin1),
::testing::Values(CompressionLevel::None, CompressionLevel::Fast,
CompressionLevel::Best)
)
);Typed Tests — Same Logic Across Multiple Types
TYPED_TEST runs identical assertions against every type in a list, which is ideal for testing generic containers or numeric algorithms.
template <typename T>
class StackTest : public ::testing::Test {
protected:
Stack<T> stack_;
};
using StackTypes = ::testing::Types<int, double, std::string>;
TYPED_TEST_SUITE(StackTest, StackTypes);
TYPED_TEST(StackTest, IsEmptyOnConstruction) {
EXPECT_TRUE(this->stack_.empty());
EXPECT_EQ(this->stack_.size(), 0u);
}
TYPED_TEST(StackTest, PushIncreasesSize) {
this->stack_.push(TypeParam{});
EXPECT_EQ(this->stack_.size(), 1u);
}GMock — Mocking in Depth
GMock lets you replace real dependencies with controlled fakes without writing boilerplate stub classes by hand. Declare mock methods with MOCK_METHOD, set expectations with EXPECT_CALL, and provide default behavior with ON_CALL.
#include <gmock/gmock.h>
// Interface under test
class Logger {
public:
virtual ~Logger() = default;
virtual void log(int level, std::string_view message) = 0;
virtual bool isEnabled(int level) const = 0;
};
// Generated mock
class MockLogger : public Logger {
public:
MOCK_METHOD(void, log, (int level, std::string_view message), (override));
MOCK_METHOD(bool, isEnabled, (int level), (const, override));
};EXPECT_CALL and ON_CALL
using namespace ::testing;
TEST(ServiceTest, LogsErrorOnFailure) {
MockLogger logger;
// ON_CALL: default behavior for all calls (no expectation failure if not called)
ON_CALL(logger, isEnabled(_)).WillByDefault(Return(true));
// EXPECT_CALL: expectation + behavior; failure if not met
EXPECT_CALL(logger, log(3, HasSubstr("timeout")))
.Times(1);
Service svc(logger);
svc.run(); // Should internally call logger.log(3, "connection timeout")
}InSequence — Enforcing Call Order
TEST(ProtocolTest, SendsHandshakeBeforeData) {
MockTransport transport;
InSequence seq; // All EXPECT_CALLs below must fire in order.
EXPECT_CALL(transport, send("HELLO")).Times(1);
EXPECT_CALL(transport, send("DATA:foo")).Times(1);
EXPECT_CALL(transport, send("BYE")).Times(1);
Protocol proto(transport);
proto.sendMessage("foo");
}SaveArg — Capturing Arguments
SaveArg<N> captures the Nth argument of a mock call for later inspection.
TEST(QueueTest, PassesCorrectJobToWorker) {
MockWorker worker;
Job capturedJob;
EXPECT_CALL(worker, execute(_))
.WillOnce(SaveArg<0>(&capturedJob));
TaskQueue queue(worker);
queue.push(Job{"render", 42});
EXPECT_EQ(capturedJob.name, "render");
EXPECT_EQ(capturedJob.priority, 42);
}Cardinality Shorthands
EXPECT_CALL(mock, method(_)).Times(AnyNumber()); // zero or more
EXPECT_CALL(mock, method(_)).Times(AtLeast(1)); // one or more
EXPECT_CALL(mock, method(_)).Times(AtMost(3)); // up to three
EXPECT_CALL(mock, method(_)).Times(Between(2, 5)); // inclusive range
EXPECT_CALL(mock, method(_)).Times(Exactly(7)); // exactly sevenMatchers
The matcher library turns assertion failures into readable diagnostics — instead of EXPECT_TRUE(Contains(...)), you write EXPECT_THAT(container, Contains(5)) and gtest prints what it expected and what it saw.
Container Matchers
#include <gmock/gmock.h>
using namespace ::testing;
TEST(VectorTest, ContainsExpectedElements) {
std::vector<int> v = {1, 2, 3, 4, 5};
EXPECT_THAT(v, Contains(3));
EXPECT_THAT(v, Not(Contains(99)));
EXPECT_THAT(v, ElementsAre(1, 2, 3, 4, 5)); // exact order
EXPECT_THAT(v, UnorderedElementsAre(5, 3, 1, 4, 2)); // any order
EXPECT_THAT(v, Each(Gt(0))); // every element > 0
EXPECT_THAT(v, SizeIs(5));
EXPECT_THAT(v, IsEmpty()); // or Not(IsEmpty())
}String Matchers
TEST(StringTest, ChecksSubstrings) {
std::string msg = "Error: file not found";
EXPECT_THAT(msg, HasSubstr("not found"));
EXPECT_THAT(msg, StartsWith("Error"));
EXPECT_THAT(msg, EndsWith("found"));
EXPECT_THAT(msg, MatchesRegex("Error:.*not found"));
EXPECT_THAT(msg, ContainsRegex("file"));
}Combining Matchers
TEST(ResponseTest, IsSuccessfulJson) {
Response r = fetchData();
EXPECT_THAT(r.statusCode, AllOf(Ge(200), Lt(300)));
EXPECT_THAT(r.contentType, AnyOf(HasSubstr("json"), HasSubstr("text")));
EXPECT_THAT(r.body, Not(IsEmpty()));
}Pointer and Optional Matchers
TEST(NodeTest, PointeeHasCorrectValue) {
auto p = std::make_unique<int>(42);
EXPECT_THAT(p.get(), Pointee(42));
std::optional<std::string> opt = "hello";
EXPECT_THAT(opt, Optional(HasSubstr("ell")));
}Custom Matchers with MATCHER
Define reusable matchers for your own types with MATCHER or MATCHER_P.
// Zero-argument custom matcher
MATCHER(IsEven, "is even") {
return (arg % 2) == 0;
}
// One-parameter custom matcher
MATCHER_P(IsDivisibleBy, n, "") {
return (arg % n) == 0;
}
TEST(CustomMatcherTest, Works) {
EXPECT_THAT(4, IsEven());
EXPECT_THAT(15, IsDivisibleBy(5));
EXPECT_THAT(std::vector<int>{2, 4, 6}, Each(IsEven()));
}Death Tests
Death tests verify that code terminates the process — catching calls to std::abort, std::terminate, or explicit exit() on invalid inputs. They are essential for testing precondition enforcement in low-level code.
#include <gtest/gtest.h>
void mustBePositive(int x) {
if (x <= 0) std::abort();
}
TEST(DeathTest, AbortsOnNonPositive) {
EXPECT_DEATH(mustBePositive(-1), ""); // any output (or none)
EXPECT_DEATH(mustBePositive(0), "");
}The second argument to EXPECT_DEATH is a regex matched against stderr output. Use it to distinguish which abort fired:
void checkBounds(int idx, int size) {
if (idx < 0 || idx >= size) {
std::fprintf(stderr, "index %d out of bounds [0, %d)\n", idx, size);
std::abort();
}
}
TEST(BoundsTest, AbortMessageContainsIndex) {
EXPECT_DEATH(checkBounds(10, 5), "10 out of bounds");
}ASSERT_DEATH and EXPECT_EXIT
// Fatal variant — stops the test on failure (like ASSERT_ vs EXPECT_)
ASSERT_DEATH(badCall(), "expected pattern");
// EXPECT_EXIT — checks exit code and stderr
EXPECT_EXIT(
std::exit(42),
::testing::ExitedWithCode(42),
""
);
// Testing abnormal termination (signal-based on POSIX)
EXPECT_EXIT(
std::abort(),
::testing::KilledBySignal(SIGABRT),
""
);Death Test Style
GoogleTest runs death tests in a child process by default ("fast" style forks before test setup; "threadsafe" re-executes the entire binary). Set the style globally or per-test:
TEST(MyTest, AbortTest) {
::testing::FLAGS_gtest_death_test_style = "threadsafe";
EXPECT_DEATH(dangerousFunction(), "");
}Prefer "threadsafe" when the code under test spawns threads; use "fast" (the default) everywhere else to keep the test suite quick.
Test Output and Filtering
The --gtest_filter flag is the fastest way to run a subset of tests during development. Patterns support * wildcards and - exclusions.
# Run exactly one test
./myapp_tests --gtest_filter=DatabaseTest.InsertAndRetrieve
# Run all tests in a suite
./myapp_tests --gtest_filter=DatabaseTest.*
# Run tests matching a substring
./myapp_tests --gtest_filter=*Insert*
# Exclude tests matching a pattern
./myapp_tests --gtest_filter=-*Slow*
# Combine inclusions and exclusions (colon separates multiple patterns)
./myapp_tests --gtest_filter=DatabaseTest.*:CacheTest.*-*Flaky*Discovery and Repetition
# Print every test name without running them
./myapp_tests --gtest_list_tests
# Run each test 20 times (useful for catching flakiness)
./myapp_tests --gtest_repeat=20
# Randomize test order (catches hidden inter-test dependencies)
./myapp_tests --gtest_shuffle
# Combine: shuffle and repeat
./myapp_tests --gtest_shuffle --gtest_repeat=50 --gtest_filter=*Flaky*CI Output
# Emit JUnit-compatible XML (consumed by Jenkins, GitLab, GitHub Actions)
./myapp_tests --gtest_output=xml:build/reports/gtest.xml
# Emit JSON report
./myapp_tests --gtest_output=json:build/reports/gtest.json
# Print all test names as they run (verbose)
./myapp_tests --gtest_print_time=1 --gtest_color=yesGitHub Actions example consuming the XML report:
- name: Run tests
run: ./build/myapp_tests --gtest_output=xml:report.xml
- name: Publish test results
uses: dorny/test-reporter@v1
with:
name: GoogleTest Results
path: report.xml
reporter: java-junitCMake Integration in Depth
gtest_discover_tests() is the preferred CMake helper — it runs the test binary with --gtest_list_tests at build time and registers each test case individually with CTest, so ctest -R MyTest works without recompiling.
Full FetchContent Setup
cmake_minimum_required(VERSION 3.21)
project(MyProject CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
include(FetchContent)
FetchContent_Declare(
googletest
URL https://github.com/google/googletest/archive/refs/tags/v1.15.2.zip
URL_HASH SHA256=7b42b4d6ed48810cfaa5cd79f3c20c861a5c8e4d5b3e5d6e2f3f5e0e3e3c3d2
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
)
# Prevent installing gtest alongside your project
set(INSTALL_GTEST OFF CACHE BOOL "" FORCE)
# On Windows: prevent overriding CRT linkage
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)
enable_testing()
add_executable(myapp_tests
tests/math_test.cpp
tests/parser_test.cpp
tests/db_test.cpp
)
target_link_libraries(myapp_tests
PRIVATE
myapp_lib
GTest::gtest_main
GTest::gmock
)
target_include_directories(myapp_tests PRIVATE ${CMAKE_SOURCE_DIR}/src)
include(GoogleTest)
gtest_discover_tests(myapp_tests
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
PROPERTIES TIMEOUT 30 # per-test timeout in seconds
)gtest_discover_tests vs gtest_add_tests
| Feature | gtest_discover_tests | gtest_add_tests |
|---|---|---|
| Discovery method | Runs binary at build time | Parses source files |
| Accuracy | Exact — handles dynamic test names | Misses TEST_P, macros |
| Requires build | Yes (binary must be linkable) | No |
| Recommended | Yes (default choice) | Legacy / cross-compilation only |
Running via CTest
# Build and run all tests
cmake -B build -DCMAKE_BUILD_TYPE=Debug
cmake --build build
ctest --test-dir build
# Only show output for failing tests (CI-friendly)
ctest --test-dir build --output-on-failure
# Run tests in parallel
ctest --test-dir build -j8
# Filter by name
ctest --test-dir build -R "DatabaseTest"
# Verbose output
ctest --test-dir build -V
# Emit JUnit XML (CTest native)
ctest --test-dir build --output-junit build/ctest-report.xmlPreset Integration (CMakePresets.json)
CMake presets keep CI and developer workflows consistent without passing flags manually.
{
"version": 6,
"configurePresets": [
{
"name": "debug",
"binaryDir": "${sourceDir}/build/debug",
"cacheVariables": { "CMAKE_BUILD_TYPE": "Debug" }
}
],
"buildPresets": [
{ "name": "debug", "configurePreset": "debug" }
],
"testPresets": [
{
"name": "debug",
"configurePreset": "debug",
"output": { "outputOnFailure": true },
"execution": { "jobs": 8, "timeout": 60 }
}
]
}cmake --preset debug
cmake --build --preset debug
ctest --preset debug