FetchContent Integration
The recommended approach for integrating Google Test into a CMake project is through FetchContent. This ensures you always get a consistent, reproducible version of the testing framework without requiring users to pre-install it system-wide.
# CMakeLists.txt — Google Test via FetchContent
cmake_minimum_required(VERSION 3.20)
project(MyProject LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Only build tests if this is the top-level project
if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
include(CTest)
endif()
if(BUILD_TESTING)
include(FetchContent)
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG v1.14.0
GIT_SHALLOW TRUE
)
# Prevent GoogleTest from overriding our compiler/linker options
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)
add_subdirectory(tests)
endif()
The GIT_SHALLOW TRUE option speeds up the clone significantly by avoiding the full repository history. Setting gtest_force_shared_crt is critical on Windows to prevent runtime library mismatches between your project and GTest.
v1.14.0) rather than tracking main. This ensures reproducible builds across developer machines and CI environments.
find_package Approach
If Google Test is already installed on the system (via a package manager or a previous build), you can use find_package() instead of downloading it every time:
# CMakeLists.txt — System-installed Google Test
cmake_minimum_required(VERSION 3.20)
project(MyProject LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
include(CTest)
if(BUILD_TESTING)
find_package(GTest 1.14 REQUIRED)
include(GoogleTest)
add_executable(my_tests
tests/test_math.cpp
tests/test_string.cpp
)
target_link_libraries(my_tests PRIVATE
GTest::gtest_main
GTest::gmock
)
gtest_discover_tests(my_tests)
endif()
A hybrid approach provides flexibility — try find_package() first, then fall back to FetchContent:
# Hybrid: prefer system GTest, fallback to FetchContent
find_package(GTest 1.14 QUIET)
if(NOT GTest_FOUND)
message(STATUS "GTest not found locally — fetching from GitHub")
include(FetchContent)
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG v1.14.0
GIT_SHALLOW TRUE
)
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)
endif()
include(GoogleTest)
GTest and GMock Linking
Google Test provides several CMake targets for different use cases. Understanding which target to link determines how your test executables are built:
# tests/CMakeLists.txt — Linking targets
# GTest::gtest — Core library (requires your own main())
# GTest::gtest_main — Core + default main() function
# GTest::gmock — Google Mock (requires your own main())
# GTest::gmock_main — Google Mock + default main()
# Most common: GTest with default main
add_executable(unit_tests
test_calculator.cpp
test_parser.cpp
)
target_link_libraries(unit_tests PRIVATE
GTest::gtest_main
my_library # Your library under test
)
# With Google Mock (includes GTest automatically)
add_executable(mock_tests
test_database.cpp
test_network.cpp
)
target_link_libraries(mock_tests PRIVATE
GTest::gmock_main
my_library
)
# Custom main() for global test setup
add_executable(integration_tests
test_main.cpp
test_integration.cpp
)
target_link_libraries(integration_tests PRIVATE
GTest::gtest
GTest::gmock
my_library
)
// test_main.cpp — Custom main with global setup
#include <gtest/gtest.h>
#include <iostream>
class GlobalSetup : public ::testing::Environment {
public:
void SetUp() override {
std::cout << "Global test environment setup\n";
// Initialize shared resources (database, network, etc.)
}
void TearDown() override {
std::cout << "Global test environment teardown\n";
}
};
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
::testing::AddGlobalTestEnvironment(new GlobalSetup);
return RUN_ALL_TESTS();
}
gtest_discover_tests() vs gtest_add_tests()
CMake provides two mechanisms for registering Google Test cases with CTest. Understanding their differences is crucial for correct test reporting:
# Recommended: gtest_discover_tests (runtime discovery)
include(GoogleTest)
add_executable(my_tests test_suite.cpp)
target_link_libraries(my_tests PRIVATE GTest::gtest_main)
# Discovers tests by running the executable with --gtest_list_tests
gtest_discover_tests(my_tests
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
PROPERTIES
LABELS "unit"
TIMEOUT 30
DISCOVERY_TIMEOUT 60
XML_OUTPUT_DIR ${CMAKE_BINARY_DIR}/test-results
)
# Alternative: gtest_add_tests (source-level parsing)
include(GoogleTest)
add_executable(my_tests test_suite.cpp)
target_link_libraries(my_tests PRIVATE GTest::gtest_main)
# Parses source files for TEST() and TEST_F() macros
gtest_add_tests(
TARGET my_tests
SOURCES test_suite.cpp
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
TEST_PREFIX "unit."
)
gtest_discover_tests() runs the compiled test binary at configure time to enumerate tests — it catches parameterized and typed tests correctly. gtest_add_tests() parses source files with regex, which is faster but misses dynamically generated tests.
gtest_discover_tests() may fail during the discovery phase. Use DISCOVERY_TIMEOUT and ensure the executable can at least enumerate tests without crashing.
Test Fixtures and Parameterized Tests
Test fixtures share common setup/teardown logic across related tests. Parameterized tests allow running the same logic with different inputs — both integrate seamlessly with CMake's test discovery:
// test_calculator.cpp — Fixtures and parameterized tests
#include <gtest/gtest.h>
#include "calculator.h"
// Test Fixture — shared setup for Calculator tests
class CalculatorTest : public ::testing::Test {
protected:
Calculator calc;
void SetUp() override {
calc.reset();
calc.setPrecision(10);
}
void TearDown() override {
// Cleanup if needed
}
};
TEST_F(CalculatorTest, AddPositiveNumbers) {
EXPECT_DOUBLE_EQ(calc.add(2.0, 3.0), 5.0);
}
TEST_F(CalculatorTest, DivideByZeroThrows) {
EXPECT_THROW(calc.divide(1.0, 0.0), std::invalid_argument);
}
// Parameterized Test — test multiple inputs
class AdditionTest : public ::testing::TestWithParam<std::tuple<double, double, double>> {};
TEST_P(AdditionTest, VerifySums) {
auto [a, b, expected] = GetParam();
Calculator calc;
EXPECT_DOUBLE_EQ(calc.add(a, b), expected);
}
INSTANTIATE_TEST_SUITE_P(
ArithmeticSuite,
AdditionTest,
::testing::Values(
std::make_tuple(1.0, 1.0, 2.0),
std::make_tuple(-1.0, 1.0, 0.0),
std::make_tuple(0.0, 0.0, 0.0),
std::make_tuple(1e10, 1e10, 2e10)
)
);
The CMake side requires no special configuration — gtest_discover_tests() automatically enumerates each parameterized instance as a separate CTest test case.
GMock Integration
Google Mock enables creating mock objects for dependency injection and behavior verification. It ships alongside Google Test and is available through the same FetchContent declaration:
// test_service.cpp — GMock usage
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "database_interface.h"
#include "user_service.h"
// Mock the database interface
class MockDatabase : public DatabaseInterface {
public:
MOCK_METHOD(bool, connect, (const std::string& uri), (override));
MOCK_METHOD(std::optional<User>, findUser, (int id), (override));
MOCK_METHOD(bool, saveUser, (const User& user), (override));
MOCK_METHOD(void, disconnect, (), (override));
};
class UserServiceTest : public ::testing::Test {
protected:
MockDatabase mockDb;
UserService service{&mockDb};
};
TEST_F(UserServiceTest, FindUserReturnsCorrectData) {
User expected{42, "Alice", "alice@example.com"};
EXPECT_CALL(mockDb, findUser(42))
.Times(1)
.WillOnce(::testing::Return(expected));
auto result = service.getUser(42);
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result->name, "Alice");
}
TEST_F(UserServiceTest, SaveUserCallsDatabase) {
User newUser{0, "Bob", "bob@example.com"};
EXPECT_CALL(mockDb, saveUser(::testing::_))
.WillOnce(::testing::Return(true));
EXPECT_TRUE(service.createUser(newUser));
}
# tests/CMakeLists.txt — GMock test executable
add_executable(service_tests
test_service.cpp
test_repository.cpp
)
target_link_libraries(service_tests PRIVATE
GTest::gmock_main
my_service_lib
)
gtest_discover_tests(service_tests
PROPERTIES LABELS "integration"
)
Death Tests Configuration
Death tests verify that code terminates correctly (via abort(), exit(), or signals). They require special configuration in CMake because they fork processes:
// test_safety.cpp — Death tests
#include <gtest/gtest.h>
#include "safety_checks.h"
TEST(SafetyDeathTest, NullPointerAborts) {
EXPECT_DEATH(dereference_pointer(nullptr), ".*null.*");
}
TEST(SafetyDeathTest, OutOfBoundsExits) {
EXPECT_EXIT(
access_array_out_of_bounds(),
::testing::ExitedWithCode(1),
"index out of range"
);
}
TEST(SafetyDeathTest, AssertionFailure) {
EXPECT_DEBUG_DEATH(
debug_assert_false(),
"Assertion.*failed"
);
}
# Death tests need special CTest properties
add_executable(death_tests test_safety.cpp)
target_link_libraries(death_tests PRIVATE GTest::gtest_main my_library)
gtest_discover_tests(death_tests
PROPERTIES
LABELS "death"
TIMEOUT 10
# Death tests may produce non-zero exit codes
# Ensure CTest doesn't treat forked process exits as failures
)
# On threaded systems, set the death test style
target_compile_definitions(death_tests PRIVATE
GTEST_FLAG_SET=1
)
DeathTest suffix — GTest runs these first to avoid issues with threads.
CI Integration Tips
Integrating Google Test with CI pipelines requires proper XML output configuration and parallel execution setup:
# CMakeLists.txt — CI-friendly test configuration
include(GoogleTest)
add_executable(all_tests
test_core.cpp
test_utils.cpp
test_networking.cpp
)
target_link_libraries(all_tests PRIVATE GTest::gtest_main my_library)
gtest_discover_tests(all_tests
# JUnit XML output for CI reporting
XML_OUTPUT_DIR ${CMAKE_BINARY_DIR}/test-results
# Prefix tests for easy filtering
TEST_PREFIX "unit."
# Prevent hangs in CI
DISCOVERY_TIMEOUT 120
PROPERTIES
TIMEOUT 60
LABELS "unit"
)
# CI build and test script
#!/bin/bash
set -e
# Configure with testing enabled
cmake -B build -S . \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_TESTING=ON
# Build (parallel)
cmake --build build --parallel $(nproc)
# Run tests with verbose output and JUnit XML
cd build
ctest --output-on-failure \
--parallel $(nproc) \
--output-junit test-results/ctest-results.xml \
--timeout 120
# For GitHub Actions, upload test-results/ as artifact
# Optional: Code coverage with GTest
option(ENABLE_COVERAGE "Enable code coverage" OFF)
if(ENABLE_COVERAGE AND CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
target_compile_options(all_tests PRIVATE --coverage -O0 -g)
target_link_options(all_tests PRIVATE --coverage)
# Add custom target for coverage report
find_program(GCOVR gcovr)
if(GCOVR)
add_custom_target(coverage
COMMAND ${GCOVR} --root ${CMAKE_SOURCE_DIR}
--exclude ".*test.*"
--xml-pretty --output coverage.xml
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
COMMENT "Generating coverage report"
)
endif()
endif()
ctest --output-on-failure so failed tests show their assertion messages. Combine with --parallel for faster feedback, and --timeout to prevent hangs from blocking your pipeline.