
本文详解如何在 Spock 框架中精确验证被测对象对 Mock 对象方法的按序调用及参数匹配,通过 then: 块分段声明实现时序敏感断言,避免仅校验调用次数导致的逻辑漏洞。
本文详解如何在 spock 框架中精确验证被测对象对 mock 对象方法的**按序调用**及**参数匹配**,通过 `then:` 块分段声明实现时序敏感断言,避免仅校验调用次数导致的逻辑漏洞。
在 Spock 单元测试中,仅验证“某方法被调用了 N 次”(如 2 * foo.bar(_))无法保证业务逻辑的正确性——尤其当方法调用顺序本身承载语义时(例如先初始化、再提交;或先写缓存、后发通知)。针对如下典型场景:
class MyClass {
private Foo foo
void myMethod(String arg1, String arg2) {
foo.bar(arg1) // 必须先执行
foo.bar(arg2) // 必须后执行
}
}若使用 2 * foo.bar(_) 断言,即使实际执行顺序为 foo.bar('b') → foo.bar('a'),测试仍会通过,从而掩盖严重逻辑缺陷。
✅ 正确做法是将 then: 块拆分为多个独立的、有序的验证段落,Spock 会严格按 then: 出现的先后顺序,依次匹配对应时间段内的调用记录:
def "myMethod calls foo.bar with 'a' before 'b'"() {
given:
def foo = Mock(Foo)
def myClass = new MyClass()
myClass.foo = foo // 注入 mock
when:
myClass.myMethod('a', 'b')
then:
1 * foo.bar('a') // 第一个 then::只匹配第一次调用,且参数必须为 'a'
then:
1 * foo.bar('b') // 第二个 then::只匹配紧随其后的下一次调用,且参数必须为 'b'
}? 关键机制说明:
- 每个 then: 块构成一个独立的交互验证阶段,Spock 按代码顺序逐段消费调用历史;
- 1 * foo.bar('a') 不仅要求调用发生,还强制其为首次调用且参数精确匹配;
- 后续 then: 自动从剩余未匹配调用中继续验证,天然保障时序与参数双重约束。
⚠️ 注意事项:
- ❌ 避免在单个 then: 中混用顺序敏感断言(如 1 * foo.bar('a') >> 1 * foo.bar('b')),Spock 不支持此语法;
- ✅ 若需验证“同一方法在不同阶段被多次调用”,可组合使用 >>(返回值)、>> { ... }(副作用)和 and:/where: 进行增强;
- ? 对更复杂的调用序列(如条件分支、循环),建议配合 1 * foo.bar(_) >> { args -> assert args[0] in ['a','b'] } 进行动态参数校验。
总结:Spock 的多 then: 结构是验证有状态交互时序的简洁而强大的原生方案。它将“调用顺序”这一隐式契约显式编码为测试结构,显著提升测试的表达力与可靠性——这正是行为驱动开发(BDD)在单元测试层面的精准落地。

















