Assertions and Parameterized Tests
Learn to use a wide range of assertions, write data-driven parameterized tests, and isolate dependencies with Google Mock.
In the last lesson, we integrated GoogleTest into our project, replacing our clunky manual test driver. We learned the basics: the TEST
and TEST_F
macros, the simple EXPECT_EQ
assertion, and how to automate test discovery with gtest_discover_tests()
.
This is a solid foundation, but GoogleTest offers a much richer set of tools. Real-world code has more complex requirements. How do you test for exceptions? How do you verify a function's behavior across dozens of different inputs without writing dozens of repetitive tests?
This lesson will answer those questions as we introduce three more advanced testing features:
- A wider range of assertion macros for handling different data types and failure modes.
- Parameterized tests for writing data-driven test cases.
- Custom matchers for creating expressive and reusable assertions for complex conditions
Assertion Macros
So far, we've only used EXPECT_EQ
, but GoogleTest provides a wide range of assertion macros for different situations. The first and most important distinction to learn is between the two "flavors" of every assertion: EXPECT_*
and ASSERT_*
.
Fatal vs. Non-Fatal Failures
EXPECT_*
: These are non-fatal assertions. If anEXPECT_*
assertion fails, it records the failure and prints an error message, but the current test function continues to run. This is what we want most of the time, particularly if the function has multiple assertions, as it allows us to see all the failures, not just the first one.ASSERT_*
: These are fatal assertions. If anASSERT_*
assertion fails, it immediately aborts the current test function. Sometimes, there will be no point in continuing the test if a fundamental assumption is violated, and it may even be unsafe to do so. In those situations, fatally failing is more appropriate.
Let's evolve our Greeter
class to see some of these new assertions in action. We'll add an "enthusiasm level" that affects the greeting:
Files
Now, let's add some new tests for this feature.
Floating-Point Comparisons with EXPECT_NEAR
Directly comparing floating-point numbers with ==
is a classic programming error due to precision issues. GoogleTest provides EXPECT_NEAR
and ASSERT_NEAR
to solve this. They take a third argument: the acceptable tolerance:
#include <gtest/gtest.h>
#include <greeter/Greeter.h>
class GreeterTest : public testing::Test {
protected:
Greeter my_greeter;
};
TEST_F(GreeterTest, EnthusiasmIsSetCorrectly) {
my_greeter.setEnthusiasm(0.7f);
EXPECT_NEAR(
my_greeter.getEnthusiasm(), 0.7f, 0.001
);
}
Exception Testing with ASSERT_THROW
Our setEnthusiasm()
method is designed to throw an exception if the input is invalid. Let's add a test to verify this behavior. EXPECT_THROW
and ASSERT_THROW
check that a given statement throws an exception of a specific type.
#include <gtest/gtest.h>
#include <greeter/Greeter.h>
class GreeterTest : public testing::Test {
protected:
Greeter my_greeter;
};
TEST_F(GreeterTest, EnthusiasmIsSetCorrectly) {
my_greeter.setEnthusiasm(0.7f);
EXPECT_NEAR(
my_greeter.getEnthusiasm(), 0.7f, 0.001
);
}
TEST_F(
GreeterTest,
SetEnthusiasmThrowsOnInvalidInput
) {
EXPECT_THROW(
my_greeter.setEnthusiasm(1.1f),
std::out_of_range
);
}
Other Useful Assertion Macros
Beyond the basics, Google Test provides a rich set of macros for various common checks. Here are some of the most frequently used:
Boolean Checks:
EXPECT_TRUE(condition)
/ASSERT_TRUE(condition)
: Verifies that a condition is true.EXPECT_FALSE(condition)
/ASSERT_FALSE(condition)
: Verifies that a condition is false.
Relational Comparisons: For numeric types, these are often more readable than using EXPECT_TRUE
with a comparison operator.
EXPECT_NE(val1, val2)
: Not equal (!=
).EXPECT_LT(val1, val2)
: Less than (<
).EXPECT_LE(val1, val2)
: Less than or equal to (<=
).EXPECT_GT(val1, val2)
: Greater than (>
).EXPECT_GE(val1, val2)
: Greater than or equal to (>=
).
String Comparisons: While EXPECT_EQ
works perfectly for std::string
, you need special macros for C-style strings (char*
).
EXPECT_STREQ(str1, str2)
: Verifies two C-style strings have the same content.EXPECT_STRCASEEQ(str1, str2)
: The case-insensitive version ofEXPECT_STREQ
.
Death Tests: For testing that code exits prematurely (e.g., via assert()
or exit()
).
EXPECT_DEATH(statement, regex)
: Verifies that a statement causes the process to terminate with a non-zero exit code and that its standard error output matches a regular expression.
The full list of available assertions are on the official docs.
Matchers
Sometimes, a simple equality or relational check isn't enough. You might want to verify that a string has a certain format, that a collection contains a specific element, or that a value falls within a complex range.
We could implement each of these checks manually within our test function, but more commonly, test libraries like Google Test allow us to define matchers that integrate with their APIs more elegantly.
Matchers are objects that represent a predicate. In Google Test, they are used with the EXPECT_THAT(value, matcher)
assertion, which reads like natural language: "I expect that this value matches this condition."
We can create any matcher (predicate function) that we might need, but The Google Mock library, which is bundled with Google Test, includes a large collection of generally useful examples.
To use them, we typically need to include the <gmock/gmock.h>
header and link against the GTest::gmock
library.
Let's update tests/CMakeLists.txt
to include it.
tests/CMakeLists.txt
cmake_minimum_required(VERSION 3.23)
find_package(GTest REQUIRED)
add_executable(GreeterTests main.cpp)
target_link_libraries(GreeterTests PRIVATE
GreeterLib
GTest::gtest
GTest::gmock # Add the gmock library
GTest::gtest_main
)
gtest_discover_tests(GreeterTests)
Here are a few of the many built-in matchers that Google Mock provides:
testing::Eq(value)
: Equivalent to==
.testing::Gt(value)
: Greater than (>
).testing::StartsWith(str)
: Checks if a string starts with a given prefix.testing::EndsWith(str)
: Checks if a string ends with a given suffix.testing::ContainsRegex(regex)
: Checks if a string contains a substring that matches a regular expression.testing::Each(matcher)
: Checks if every element in a container matches an inner matcher.testing::AllOf(m1, m2, ...)
: A logical AND; succeeds if all nested matchers succeed.
The official docs contain the full list of available matchers.
Practical Example
In our parameterized test, we checked for the exact number of exclamation marks. This is a bit brittle; what if we only care that the greeting has the correct base string and ends with some number of exclamation marks? A matcher is perfect for this.
Let's add a test that uses a regular expression matcher to verify the format:
tests/main.cpp
#include <gtest/gtest.h>
#include <gmock/gmock.h> // Include for matchers
#include <greeter/Greeter.h>
#include <tuple>
class GreeterTest : public testing::Test {
protected:
Greeter my_greeter;
};
TEST_F(GreeterTest, GreetHasCorrectFormat) {
my_greeter.setEnthusiasm(0.8f);
EXPECT_THAT(
my_greeter.greet(),
testing::MatchesRegex("Hello from the Greeter class!*")
);
}
The *
character is a special regex token that matches any quantity (including 0) of the proceeding token. We have on regular expressions in our C++ course.
In this example, our test is ensuring that the function returns "Hello from the Greeter class" followed by any number of exclamation marks.
cmake --build --preset default
ctest --preset default
Test project D:/Projects/cmake/build
Start 1: GreeterTest.GreetHasCorrectFormat
1/1 Test #1: GreeterTest.GreetHasCorrectFormat ... Passed 0.01 sec
100% tests passed, 0 tests failed out of 1
Total Test time (real) = 0.04 sec
This test is more robust to small, insignificant changes in the implementation of greet()
.
Parameterized Tests
Let's go back to our previous example where we were testing the exact output of greet()
, not just testing the general format.
The exact behavior that depends an externally-provided parameter - the enthusiasm level. What if we want to test the greet()
method for several different enthusiasm levels? We could write a separate test for each one:
TEST_F(GreeterTest, GreetsWithNoExclamations) {
my_greeter.setEnthusiasm(0.1f);
EXPECT_EQ(
my_greeter.greet(),
"Hello from the Greeter class"
);
}
TEST_F(GreeterTest, GreetsWithOneExclamation) {
my_greeter.setEnthusiasm(0.4f);
EXPECT_EQ(
my_greeter.greet(),
"Hello from the Greeter class!"
);
}
// ... and so on ...
This is repetitive and doesn't scale up to real-world scenarios where we might want to test dozens or hundreds of different parameter combinations. The solution is a parameterized test. This allows you to run the same test logic with a wide range of different input values.
Let's refactor our enthusiasm tests into a single parameterized test. The setup involves four steps.
Step 1: Use TestWithParam
First, we'll update our a test fixture class to inherit from testing::TestWithParam<T>
, instead of testing::Test
.
The template parameter T
in TestWithParam
is the type of our parameter. We often use a std::tuple
for this, as our parameter typically contain multiple values of different types.
In our case, our tuple will contain a float
representing the enthusiasm_
level, and a std::string
representing what we expect our function to return given that enthusiasm_
level:
tests/main.cpp
#include <gtest/gtest.h>
#include <greeter/Greeter.h>
#include <tuple>
class GreeterEnthusiasmTest :
public testing::TestWithParam<
std::tuple<float, std::string>
>
{
protected:
Greeter my_greeter;
};
// ...
Step 2: Writing a Parameterized Test with TEST_P
Next, we use the TEST_P
macro (the 'P' is for Parameterized) instead of TEST_F
.
Inside the test, we can access the current parameter value with the GetParam()
method.
We'll pull our enthusiasm and expected greeting from our tuple. We'll then set our enthusiasm to that level, and assert that the greet()
method then returns the expected string:
tests/main.cpp
#include <gtest/gtest.h>
#include <greeter/Greeter.h>
#include <tuple>
class GreeterEnthusiasmTest :
public testing::TestWithParam<
std::tuple<float, std::string>
>
{
protected:
Greeter my_greeter;
};
TEST_P(
GreeterEnthusiasmTest,
GreetReturnsCorrectExclamations
) {
auto [enthusiasm, expected_greeting]{GetParam()};
my_greeter.setEnthusiasm(enthusiasm);
EXPECT_EQ(my_greeter.greet(), expected_greeting);
}
// ...
Step 3: Instantiating Tests
Finally, we just need to instantiate our tests with different parameter values.
We use the INSTANTIATE_TEST_SUITE_P
macro for this, passing a name as the first argument, our TEST_P
's name as the second argument, and the set of parameters we want to test as the third.
The testing::Values()
function helps us to provide these parameters. This allowing us to now instantiate each test simply by constructing a parameter - often a single line of code:
tests/main.cpp
#include <gtest/gtest.h>
#include <greeter/Greeter.h>
#include <tuple>
class GreeterEnthusiasmTest :
public testing::TestWithParam<
std::tuple<float, std::string>
>
{
protected:
Greeter my_greeter;
};
TEST_P(
GreeterEnthusiasmTest,
GreetReturnsCorrectExclamations
) {
auto [enthusiasm, expected_greeting]{GetParam()};
my_greeter.setEnthusiasm(enthusiasm);
EXPECT_EQ(my_greeter.greet(), expected_greeting);
}
INSTANTIATE_TEST_SUITE_P(
EnthusiasmLevels,
GreeterEnthusiasmTest,
testing::Values(
std::make_tuple(0.0f, "Hello from the Greeter class"),
std::make_tuple(0.3f, "Hello from the Greeter class"),
std::make_tuple(0.4f, "Hello from the Greeter class!"),
std::make_tuple(0.7f, "Hello from the Greeter class!!"),
std::make_tuple(1.0f, "Hello from the Greeter class!!!")
)
);
Step 4: Running the Tests
When we run our tests now, CTest and Google Test will automatically generate and run five separate test cases based on this one definition, each with a different set of inputs from the Values
list.
cmake --build --preset default
ctest --preset default
...
100% tests passed, 0 tests failed out of 5
...
Summary
This lesson introduced most of the core features we need to write tests.
- Assertions: We learned the difference between fatal
ASSERT_*
and non-fatalEXPECT_*
assertions, and saw specialized assertions for floating-point numbers (EXPECT_NEAR
) and exceptions (ASSERT_THROW
). - Parameterized Tests: We saw how to use
TEST_P
andINSTANTIATE_TEST_SUITE_P
to eliminate repetitive code by running the same test logic against a large set of input data. - Custom Matchers: We learned to use
EXPECT_THAT
with Google Mock's library of matchers to create expressive assertions for complex conditions, such as matching a regular expression.
Dependency Injection and Mocking
Learn how to isolate dependencies that our project uses, and then how to replace those dependencies at test time using Google Mock.