首页
/ GoogleTest(GoogleMock)Mock 对象完整参考:MOCK_METHOD、EXPECT_CALL、ON_CALL 与 Mock 类的源码级解析

GoogleTest(GoogleMock)Mock 对象完整参考:MOCK_METHOD、EXPECT_CALL、ON_CALL 与 Mock 类的源码级解析

2026-09-05 14:08:36作者:温艾琴Wonderful

本文基于 docs/reference/mocking.md 参考文档,系统梳理 GoogleTest(GoogleMock)中创建和使用 Mock 对象的全部核心设施:MOCK_METHOD 宏、EXPECT_CALL / ON_CALL 语句及其可链式调用的全部修饰子句,以及 DefaultValueNiceMockStrictMock 等配套类。读完本文后,你可以直接写出可编译运行的 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 参),并做了三重编译期校验:

  1. 括号校验GMOCK_INTERNAL_ASSERT_PARENTHESIS(_Args) 要求参数列表必须整体用括号包裹,否则触发 static_assert 失败;
  2. 签名校验GMOCK_INTERNAL_ASSERT_VALID_SIGNATURE 断言拼出的类型必须是函数类型,且参数个数与声明一致。当返回类型含未加括号保护的逗号时,签名推导会错位,此处会直接报错,提示"返回类型是否含有未加保护的逗号";
  3. 限定符校验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_METHOD0MOCK_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: 区段中,无论被模拟的方法在基类中是 publicprotected 还是 private——因为 EXPECT_CALL 需要通过 publicgmock_##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) 期望被调用 mn 次(含两端)。
Exactly(n) 或直接写 n 期望恰好被调用 n 次;若 n 为 0,则该方法绝不应被调用。

省略 Times 子句时,GoogleTest 按如下规则推断基数

  • 若既没有 WillOnce 也没有 WillRepeatedly,推断为 Times(1)(默认期望恰好 1 次);
  • 若有 nn ≥ 1)个 WillOnce 且无 WillRepeatedly,推断为 Times(n)
  • 若有 nn ≥ 0)个 WillOnce 且有 1 个 WillRepeatedly,推断为 Times(AtLeast(n))

Times 子句对同一期望最多使用一次。

2.3 InSequence:顺序约束

.InSequence(sequences...) 指定该调用所属的顺序序列。参数 sequences... 是任意数量的 Sequence 对象。分配到同一序列的期望,按声明顺序依次发生

例如期望 my_mockReset() 先于 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 子句可使用任意次。顺序机制的类说明见下文 SequenceInSequence 类。

2.4 After:前置期望依赖

.After(expectations...) 指定该调用必须发生在全部列出的期望之后。参数 expectations... 最多为 5 个 ExpectationExpectationSet 对象。示例:期望 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 次调用时返回 123

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"

两条关键语义:

  1. 优先级:若存在匹配的 EXPECT_CALL 语句,其 WillOnce / WillRepeatedly 动作会覆盖(supersede) ON_CALL 的默认动作;
  2. 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 类,但不能是另一个 NiceMockNaggyMockStrictMock(即不允许嵌套包装)。

三者的用法与直接使用 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 版本则直接失败测试。

已知限制(文档明确列出,且与源码一致):

  1. 包装类只对直接在 T 的类定义中用 MOCK_METHOD 宏定义的 Mock 方法生效。若 Mock 方法定义在 T 的某个基类中,警告可能仍会产生(对 StrictMock,失败也可能不会发生);
  2. T 的析构函数不是 virtual,包装类可能无法正确工作;
  3. 嵌套包装不被支持(模板参数不能是另一层包装类)。

从源码结构看,这些行为的实现在 gmock-nice-strict.hNiceMock<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 为主体;配套的三份姊妹参考文档可进一步展开细节:

源码与测试索引(便于继续深入):

需要注意的适用前提:本文描述的内容以当前仓库代码为准;MOCK_METHOD 的 3/4 参变参形式、final 限定符支持以及 RetiresOnSaturation 等特性均为当前版本源码中实际存在的能力(final 限定符在 ValidateSpec 中可见,但参考文档的限定符表中未单列)。在旧版本代码库中迁移示例代码时,建议先核对目标版本的 googlemock/README.md 与 CHANGELOG。

登录后查看全文
热门项目推荐
相关项目推荐