GoogleTest(GoogleMock)Mock 对象完整参考:MOCK_METHOD、EXPECT_CALL、ON_CALL 与 Mock 类的源码级解析
本文基于 docs/reference/mocking.md 参考文档,系统梳理 GoogleTest(GoogleMock)中创建和使用 Mock 对象的全部核心设施:MOCK_METHOD 宏、EXPECT_CALL / ON_CALL 语句及其可链式调用的全部修饰子句,以及 DefaultValue、NiceMock、StrictMock 等配套类。读完本文后,你可以直接写出可编译运行的 Mock 类、精确控制调用次数与顺序、处理含逗号参数类型,并结合 gmock-function-mocker.h 等源码理解这些宏与类在底层的展开机制。
使用这些设施的前提是在测试代码中包含 #include <gmock/gmock.h>,本文所有代码示例均以此为基础。
一、宏参考(Macros)
1. MOCK_METHOD:定义 Mock 方法
MOCK_METHOD 用于在 Mock 类内定义一个模拟方法,支持两种签名形式:
MOCK_METHOD(return_type, method_name, (args...));
MOCK_METHOD(return_type, method_name, (args...), (specs...));
其含义是:在 Mock 类中定义一个参数为 (args...)、返回类型为 return_type 的模拟方法 method_name。宏的参数布局刻意与方法声明保持镜像关系。
可选的第四个参数 specs... 是逗号分隔的限定符(qualifier)列表,用于调整生成方法的特性。官方接受的限定符及其含义如下:
| 限定符 | 含义 |
|---|---|
const |
使 Mock 方法成为 const 方法。覆写基类 const 方法时必须使用。 |
override |
为方法添加 override 标记。覆写 virtual 方法时推荐使用。 |
noexcept |
为方法添加 noexcept 标记。覆写 noexcept 方法时必须使用。 |
Calltype(calltype) |
设置方法的调用类型,例如 Calltype(STDMETHODCALLTYPE),在 Windows 平台上有用。 |
ref(qualifier) |
为方法添加指定的引用限定符,例如 ref(&) 或 ref(&&)。覆写带引用限定符的方法时必须使用。 |
源码视角:宏是如何展开的
从源码结构看,MOCK_METHOD 的完整实现位于 gmock-function-mocker.h。该宏通过变参宏分发机制 GMOCK_PP_VARIADIC_CALL(GMOCK_INTERNAL_MOCK_METHOD_ARG_, ...) 按实参个数分派到 GMOCK_INTERNAL_MOCK_METHOD_ARG_3 或 _ARG_4(3 参或 4 参),并做了三重编译期校验:
- 括号校验:
GMOCK_INTERNAL_ASSERT_PARENTHESIS(_Args)要求参数列表必须整体用括号包裹,否则触发static_assert失败; - 签名校验:
GMOCK_INTERNAL_ASSERT_VALID_SIGNATURE断言拼出的类型必须是函数类型,且参数个数与声明一致。当返回类型含未加括号保护的逗号时,签名推导会错位,此处会直接报错,提示"返回类型是否含有未加保护的逗号"; - 限定符校验:
GMOCK_INTERNAL_ASSERT_VALID_SPEC逐一检查每个 spec 元素是否为const/override/noexcept/ref/Calltype等合法 token(见 ValidateSpec)。
展开后的产物由三部分组成(见 gmock-function-mocker.h 展开主体):
- 一个真正的成员函数体:将实参原样转发给内部的
FunctionMocker<函数签名>成员的Invoke(...)调用,真正的方法调度就发生在这里; - 一个名为
gmock_##method_name的重载,供EXPECT_CALL/ON_CALL匹配时取回MockSpec<函数签名>对象,并以.With(matchers...)携带参数匹配器; - 一个
mutable FunctionMocker<...>成员(命名为GMOCK_MOCKER_(参数个数, const性, 方法名)),负责持有该 Mock 方法的所有期望与默认动作。
另外源码还保留了一组 MOCK_METHOD0~MOCK_METHOD10(及 _T 后缀变体)的旧式宏(gmock-function-mocker.h#L357 起),它们按固定参数个数生成 Mock 方法,是 MOCK_METHOD 出现前的写法,新项目应直接使用统一的 MOCK_METHOD。
实战坑点:参数中含逗号时必须额外加括号
文档特别强调:当参数类型中含有逗号(如模板实参列表)且未额外加括号时,预处理器会把逗号误当作宏参数分隔符,导致解析错误。文档给出的完整示例如下:
class MyMock {
public:
// 以下 2 行因参数中含逗号而编译失败:
MOCK_METHOD(std::pair<bool, int>, GetPair, ()); // Error!
MOCK_METHOD(bool, CheckMap, (std::map<int, double>, bool)); // Error!
// 解决方案一:给含逗号的参数类型额外包一层括号:
MOCK_METHOD((std::pair<bool, int>), GetPair, ());
MOCK_METHOD(bool, CheckMap, ((std::map<int, double>), bool));
// 解决方案二:使用类型别名:
using BoolAndInt = std::pair<bool, int>;
MOCK_METHOD(BoolAndInt, GetPair, ());
using MapIntDouble = std::map<int, double>;
MOCK_METHOD(bool, CheckMap, (MapIntDouble, bool));
};
这一点与上面源码分析完全对应:GMOCK_PP_NARG0 _Args 统计参数个数、GMOCK_INTERNAL_SIGNATURE 推导函数类型时,一旦逗号位置错乱,static_assert 就会给出 "return type contains unprotected comma" 类提示。
还有一条访问权限规则必须牢记:MOCK_METHOD 必须写在 Mock 类的 public: 区段中,无论被模拟的方法在基类中是 public、protected 还是 private——因为 EXPECT_CALL 需要通过 public 的 gmock_##method_name 成员去取回期望对象。
2. EXPECT_CALL:声明"期望"
EXPECT_CALL(mock_object, method_name(matchers...)) 创建一个期望(expectation):要求对象 mock_object 的方法 method_name 以匹配给定匹配器 matchers... 的实参被调用。EXPECT_CALL 必须出现在所有会触发该 Mock 对象调用的代码之前(即期望先于被测代码声明)。
matchers... 是与方法各实参一一对应的逗号分隔匹配器列表(匹配器用法见 Matchers 参考)。只有当实参与所有匹配器都相符时,该期望才对该次调用生效。如果整体省略 (matchers...),行为等价于每个参数都用通配匹配器(wildcard matcher _),见 通配匹配器。
期望支持以下可链式调用的修饰子句,且必须按此顺序使用:
EXPECT_CALL(mock_object, method_name(matchers...))
.With(multi_argument_matcher) // 最多使用一次
.Times(cardinality) // 最多使用一次
.InSequence(sequences...) // 可使用任意次
.After(expectations...) // 可使用任意次
.WillOnce(action) // 可使用任意次
.WillRepeatedly(action) // 最多使用一次
.RetiresOnSaturation(); // 最多使用一次
下面逐条展开各子句的语义、限制与示例。
2.1 With:多参数联合匹配
.With(multi_argument_matcher) 将期望限定为:仅当所有实参作为一个整体匹配多参数匹配器 multi_argument_matcher 时才适用。
GoogleTest 会把全部实参打包成一个 std::tuple 传入该匹配器,因此 multi_argument_matcher 的类型必须是 Matcher<std::tuple<A1, ..., An>>,其中 A1, ..., An 为各实参类型。示例:期望 my_mock.SetPosition() 被任意两个实参调用且第一个实参小于第二个:
using ::testing::_;
using ::testing::Lt;
...
EXPECT_CALL(my_mock, SetPosition(_, _))
.With(Lt());
GoogleTest 内置了一批针对二元组的多参数匹配器(包括示例中的 Lt()),完整列表见 多参数匹配器。
使用限制:With 子句对同一期望最多使用一次,且必须是第一个子句。
2.2 Times:调用次数(基数)
.Times(cardinality) 指定该 Mock 方法期望被调用的次数。cardinality 表示期望的调用次数,可取以下任意一种(均定义在 ::testing 命名空间,实现见 gmock-cardinalities.h):
| 基数表达式 | 含义 |
|---|---|
AnyNumber() |
可被调用任意次数。 |
AtLeast(n) |
期望至少被调用 n 次。 |
AtMost(n) |
期望至多被调用 n 次。 |
Between(m, n) |
期望被调用 m 到 n 次(含两端)。 |
Exactly(n) 或直接写 n |
期望恰好被调用 n 次;若 n 为 0,则该方法绝不应被调用。 |
省略 Times 子句时,GoogleTest 按如下规则推断基数:
- 若既没有
WillOnce也没有WillRepeatedly,推断为Times(1)(默认期望恰好 1 次); - 若有 n(n ≥ 1)个
WillOnce且无WillRepeatedly,推断为Times(n); - 若有 n(n ≥ 0)个
WillOnce且有 1 个WillRepeatedly,推断为Times(AtLeast(n))。
Times 子句对同一期望最多使用一次。
2.3 InSequence:顺序约束
.InSequence(sequences...) 指定该调用所属的顺序序列。参数 sequences... 是任意数量的 Sequence 对象。分配到同一序列的期望,按声明顺序依次发生。
例如期望 my_mock 的 Reset() 先于 GetSize() 与 Describe() 被调用,而后两者之间顺序不限:
using ::testing::Sequence;
Sequence s1, s2;
...
EXPECT_CALL(my_mock, Reset())
.InSequence(s1, s2);
EXPECT_CALL(my_mock, GetSize())
.InSequence(s1);
EXPECT_CALL(my_mock, Describe())
.InSequence(s2);
InSequence 子句可使用任意次。顺序机制的类说明见下文 Sequence 与 InSequence 类。
2.4 After:前置期望依赖
.After(expectations...) 指定该调用必须发生在全部列出的期望之后。参数 expectations... 最多为 5 个 Expectation 或 ExpectationSet 对象。示例:期望 Describe() 只在 InitX() 和 InitY() 都调用过之后才被调用:
using ::testing::Expectation;
...
Expectation init_x = EXPECT_CALL(my_mock, InitX());
Expectation init_y = EXPECT_CALL(my_mock, InitY());
EXPECT_CALL(my_mock, Describe())
.After(init_x, init_y);
当前置期望数量较多或动态变化时,ExpectationSet 非常有用:
using ::testing::ExpectationSet;
...
ExpectationSet all_inits;
// 收集所有 InitElement() 调用的期望
for (int i = 0; i < element_count; i++) {
all_inits += EXPECT_CALL(my_mock, InitElement(i));
}
EXPECT_CALL(my_mock, Describe())
.After(all_inits); // 期望 Describe() 在所有 InitElement() 之后调用
After 子句可使用任意次。
2.5 WillOnce:第 N 次匹配的返回行为
.WillOnce(action) 指定单个匹配调用的实际行为。参数 action 代表该次调用将执行的动作(action),内置动作列表见 Actions 参考。
WillOnce 会在未显式写 Times 时隐式地设定基数,推断规则见上文 Times。每次匹配的调用按声明顺序依次消费下一个动作。例如指定 my_mock.GetNumber() 恰好被调用 3 次,并分别在第 1、2、3 次调用时返回 1、2、3:
using ::testing::Return;
...
EXPECT_CALL(my_mock, GetNumber())
.WillOnce(Return(1))
.WillOnce(Return(2))
.WillOnce(Return(3));
WillOnce 子句可使用任意次。与 WillRepeatedly 不同,每个 WillOnce 传入的动作至多只被执行一次,因此它可以是移动独占类型(move-only),也可以具有 && 限定(右值引用限定)的调用运算符。
2.6 WillRepeatedly:剩余匹配的默认行为
.WillRepeatedly(action) 指定后续所有匹配调用的行为,在(如有)全部 WillOnce 动作消费完之后生效。示例:
using ::testing::Return;
...
EXPECT_CALL(my_mock, GetName())
.WillRepeatedly(Return("John Doe")); // 所有调用都返回 "John Doe"
EXPECT_CALL(my_mock, GetNumber())
.WillOnce(Return(42)) // 第 1 次调用返回 42
.WillRepeatedly(Return(7)); // 之后所有调用返回 7
同样,WillRepeatedly 也会隐式设定基数(见 Times)。该子句对同一期望最多使用一次。
2.7 RetiresOnSaturation:饱和后退役
.RetiresOnSaturation() 表示当期望的调用次数达到上限后,该期望即告"饱和(saturated)"并退役(retire)——不再匹配任何后续调用。
该子句仅对有上界的基数有意义。文档给出的示例非常典型:
using ::testing::_;
using ::testing::AnyNumber;
...
EXPECT_CALL(my_mock, SetNumber(_)) // 期望 1
.Times(AnyNumber());
EXPECT_CALL(my_mock, SetNumber(7)) // 期望 2
.Times(2)
.RetiresOnSaturation();
行为解析:前两次 my_mock.SetNumber(7) 匹配期望 2,达到上限 2 后期望 2 退役;第三次 SetNumber(7) 转而匹配期望 1。若没有 RetiresOnSaturation(),第三次 SetNumber(7) 仍会再次匹配期望 2,从而因超出 2 次上限而产生测试失败。
RetiresOnSaturation 子句最多使用一次,且必须是最后一个子句。
3. ON_CALL:设定默认行为(不设期望)
ON_CALL(mock_object, method_name(matchers...)) 定义当 method_name 以匹配 matchers... 的实参被调用时应发生什么,必须再附加一个修饰子句来指定行为。与 EXPECT_CALL 的关键区别是:ON_CALL 不设置任何"该方法必须被调用"的期望。
匹配器规则与 EXPECT_CALL 相同:实参与所有匹配器相符时才生效;省略 (matchers...) 等价于每个参数用通配匹配器 _(见 通配匹配器)。
可用子句及其顺序:
ON_CALL(mock_object, method_name(matchers...))
.With(multi_argument_matcher) // 最多使用一次
.WillByDefault(action); // 必须使用
3.1 With:多参数联合匹配
语义与 EXPECT_CALL.With 一致:把全部实参打包为元组传入多参数匹配器,匹配器类型须为 Matcher<std::tuple<A1, ..., An>>。示例——当 my_mock.SetPosition() 以"第一实参小于第二实参"的任意两参被调用时的默认行为:
using ::testing::_;
using ::testing::Lt;
using ::testing::Return;
...
ON_CALL(my_mock, SetPosition(_, _))
.With(Lt())
.WillByDefault(Return(true));
With 子句对每条 ON_CALL 语句最多使用一次。
3.2 WillByDefault:默认动作
.WillByDefault(action) 指定匹配到的 Mock 方法调用的默认行为。示例:
using ::testing::Return;
...
ON_CALL(my_mock, Greet())
.WillByDefault(Return("hello")); // 默认调用 Greet() 返回 "hello"
两条关键语义:
- 优先级:若存在匹配的
EXPECT_CALL语句,其WillOnce/WillRepeatedly动作会覆盖(supersede)ON_CALL的默认动作; WillByDefault子句必须恰好使用一次。
4. 底层线程安全设计(源码补充)
从源码结构看,gmock-spec-builders.h 中定义了一个全局互斥量 g_gmock_mutex,保护 Mock 对象注册表、所有 FunctionMocker 与所有期望。源码注释解释了设计动机:当 Mock 方法 Foo() 被调用时,它需要查询期望列表以选择命中哪一条;若另一个线程同时调用 Mock 方法(Foo() 或其他方法),可能干扰使用 InSequence() 时期望的"退役"属性,进而影响期望选择。因此 GoogleMock 对所有 Mock 函数调用做串行化,保证 Mock 对象状态的一致性。这意味着在多线程测试中,对 Mock 方法的并发调用在框架内部是顺序执行的。
二、类参考(Classes)
1. DefaultValue:为类型设定全局默认返回值
::testing::DefaultValue<T> 允许用户为类型 T 指定默认值。T 须可拷贝且公开可析构(即一切可用作函数返回类型的类型)。对返回类型为 T 的 Mock 函数,凡是未指定动作的调用都返回此默认值。
它提供三个静态方法来管理默认值(声明见 gmock-spec-builders.h):
// 设置将返回的默认值。T 须可拷贝构造。
DefaultValue<T>::Set(value);
// 设置一个工厂函数,按需调用。T 须可移动构造。
T MakeT();
DefaultValue<T>::SetFactory(&MakeT);
// 清除默认值。
DefaultValue<T>::Clear();
2. NiceMock / NaggyMock / StrictMock:三种"严格度"
三者都是对 Mock 类 T 的包装模板,专门用来改变 Mock 对象面对**未有趣调用(uninteresting calls,即没有任何期望或默认动作声明的调用)**时的反应(未有趣调用与"未预期调用"的区别见 Mock cookbook 相应章节):
::testing::NiceMock<T>— 对未有趣调用抑制警告;::testing::NaggyMock<T>— 对未有趣调用产生警告;普通 Mock 对象T的默认行为与NaggyMock<T>相同;::testing::StrictMock<T>— 对未有趣调用直接产生测试失败。
模板参数 T 可以是任何 Mock 类,但不能是另一个 NiceMock、NaggyMock 或 StrictMock(即不允许嵌套包装)。
三者的用法与直接使用 T 类似:包装类是 T 的子类,因此凡接受 T 对象之处都可接受包装对象;此外,包装类可以接受 T 的任意构造函数参数。示例分别如下:
// 抑制警告:
using ::testing::NiceMock;
...
NiceMock<MockClass> my_mock("some", "args");
EXPECT_CALL(my_mock, DoSomething());
... 使用 my_mock 的代码 ...
// 产生警告(也是裸 Mock 对象的默认行为):
using ::testing::NaggyMock;
...
NaggyMock<MockClass> my_mock("some", "args");
EXPECT_CALL(my_mock, DoSomething());
... 使用 my_mock 的代码 ...
// 产生测试失败:
using ::testing::StrictMock;
...
StrictMock<MockClass> my_mock("some", "args");
EXPECT_CALL(my_mock, DoSomething());
... 使用 my_mock 的代码 ...
以上例中,凡调用了 DoSomething() 之外的方法,NiceMock 版本静默放行,NaggyMock 版本打印警告,StrictMock 版本则直接失败测试。
已知限制(文档明确列出,且与源码一致):
- 包装类只对直接在
T的类定义中用MOCK_METHOD宏定义的 Mock 方法生效。若 Mock 方法定义在T的某个基类中,警告可能仍会产生(对StrictMock,失败也可能不会发生); - 若
T的析构函数不是virtual,包装类可能无法正确工作; - 嵌套包装不被支持(模板参数不能是另一层包装类)。
从源码结构看,这些行为的实现在 gmock-nice-strict.h:NiceMock<T>、NaggyMock<T>、StrictMock<T> 分别委托给 NiceMockImpl<T> / NaggyMockImpl<T> / StrictMockImpl<T>(见 NiceMockImpl 定义 一带),这些实现类在包装对象构造时把底层 T 对象注册进 Mock 对象注册表并打上严格的度标记;文件头部的注释(gmock-nice-strict.h#L47-L57)同样确认了"继承构造函数"与"仅对直接在 T 中定义的 Mock 方法生效"这两点限制。相关行为可在测试 gmock-nice-strict_test.cc 中核对。
3. Sequence:期望时间序列
::testing::Sequence(定义见 gmock-spec-builders.h#L615)表示一个期望的时间序列对象。用法即上文 InSequence 子句:把同一个 Sequence 对象分发给多个期望,这些期望即被约束为按声明顺序执行。
4. InSequence:作用域式隐式序列
::testing::InSequence(定义见 gmock-spec-builders.h#L653)的对象会使其作用域内出现的所有期望自动进入一个匿名序列,从而更简洁地表达"一组期望按声明顺序发生":
using ::testing::InSequence;
{
InSequence seq;
// 以下期望按声明顺序发生。
EXPECT_CALL(...);
EXPECT_CALL(...);
...
EXPECT_CALL(...);
}
InSequence 对象的命名无关紧要,它只是序列的作用域标记。
5. Expectation 与 ExpectationSet:期望句柄与期望集合
::testing::Expectation 表示由 EXPECT_CALL 创建的一条调用期望,可以直接接收其返回值:
using ::testing::Expectation;
Expectation my_expectation = EXPECT_CALL(...);
它在表达期望的顺序依赖(即 After 子句)时非常有用。
::testing::ExpectationSet 表示一组调用期望,用 += 运算符把 Expectation 加入集合:
using ::testing::ExpectationSet;
ExpectationSet my_expectations;
my_expectations += EXPECT_CALL(...);
典型用途与 Expectation 相同:服务于 After 子句,特别适合"前置期望数量多或动态生成"的场景(见上文 After 的循环收集示例)。
三、配套文档与测试代码索引
本文以官方参考文档 docs/reference/mocking.md 为主体;配套的三份姊妹参考文档可进一步展开细节:
- 匹配器(matcher)完整列表:docs/reference/matchers.md
- 动作(action)完整列表:docs/reference/actions.md
- Mock 入门与实战 cookbook:docs/gmock_for_dummies.md、docs/gmock_cook_book.md
源码与测试索引(便于继续深入):
MOCK_METHOD宏展开实现:googlemock/include/gmock/gmock-function-mocker.h- 期望/顺序/严格度的核心类型(
MockSpec、Sequence、InSequence、ExpectationSet等):googlemock/include/gmock/gmock-spec-builders.h - NiceMock / NaggyMock / StrictMock 实现:googlemock/include/gmock/gmock-nice-strict.h
- 基数(cardinality)类型:googlemock/include/gmock/gmock-cardinalities.h
- 期望子句的行为测试:googlemock/test/gmock-spec-builders_test.cc
- NiceMock 等包装类的行为测试:googlemock/test/gmock-nice-strict_test.cc
- 基数行为测试:googlemock/test/gmock-cardinalities_test.cc
需要注意的适用前提:本文描述的内容以当前仓库代码为准;MOCK_METHOD 的 3/4 参变参形式、final 限定符支持以及 RetiresOnSaturation 等特性均为当前版本源码中实际存在的能力(final 限定符在 ValidateSpec 中可见,但参考文档的限定符表中未单列)。在旧版本代码库中迁移示例代码时,建议先核对目标版本的 googlemock/README.md 与 CHANGELOG。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00