Table of Contents

  1. FetchContent Integration
  2. find_package Approach
  3. GTest and GMock Linking
  4. gtest_discover_tests() vs gtest_add_tests()
  5. Test Fixtures and Parameterized Tests
  6. GMock Integration
  7. Death Tests Configuration
  8. CI Integration Tips
Back to CMake Mastery Series

Google Test

June 4, 2026 Wasil Zafar 10 min read

The definitive guide to integrating Google Test and Google Mock with modern CMake — FetchContent setup, automatic test discovery, parameterized tests, and continuous integration patterns.

Testing

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.

Best Practice: Always pin a specific GTest tag (e.g., 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."
)
Key Difference: 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.
Pitfall: If your test executable requires runtime resources (shared libraries, data files) that aren't available at build time, 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
)
Pitfall: Death tests are not supported on all platforms equally. On Windows, they use a different mechanism than Unix fork(). Name death test suites with a 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()
CI Best Practices: Always use 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.