You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让Groovy Spock测试用equals而非==比较调用参数

Spring Groovy测试Mock参数匹配失败问题

我为Spring应用编写了如下Groovy测试:

given:
    def mapProperties = new JSONObject().put(
      "eligibility", "true").put(
      "group", "group1")
when:
    trackingService.sendUnifiedOnboardingAssignmentUserUpdate()
then:
    1 * trackingCollector.send(new UserUpdate(mapProperties))

Tracking Collector是外部组件,在我继承的Specification中定义为Spring Bean:

@SpringBean
TrackingCollector trackingCollector = Mock()

trackingService位于领域层,已通过Autowired注入。

测试运行失败,报错信息如下:

Too few invocations for:

1 * trackingCollector.send(new UserUpdate(mapProperties))   (0 invocations)

Unmatched invocations (ordered by similarity):

1 * trackingCollector.send(UserUpdate(super=Event(properties={"group":"group1","eligibility":"true"})))
One or more arguments(s) didn't match:
0: argument == expected
   |        |  |
   |        |  UserUpdate(super=Event(properties={"group":"group1","eligibility":"true"})) (com.package.tracking.collector.UserUpdate@4fe5aac9)
   |        false
   UserUpdate(super=Event(properties={"group":"group1","eligibility":"true"})) (com.package.tracking.collector.UserUpdate@48bb5a69)

两个UserUpdate对象内容完全一致,仅引用地址不同。为什么Groovy不检查对象实际值而比较引用?该如何修改让测试通过?


问题原因

Groovy结合Spock进行Mock参数匹配时,默认依赖对象的equals()方法判断是否相等。如果你的UserUpdate或其父类Event没有正确重写equals()和hashCode(),就会退化为Java原生的引用比较——即只有同一对象实例才会被判定为相等。从报错信息看,两个对象内容一致但引用不同,说明UserUpdate类的equals()逻辑未按内容实现。

解决方案

1. 重写UserUpdate/Event的equals()和hashCode()

这是最规范的长期解决方案,确保对象按内容比较。示例代码(Groovy写法):

class UserUpdate extends Event {
    // 字段和构造方法

    @Override
    boolean equals(Object o) {
        if (this.is(o)) return true
        if (!(o instanceof UserUpdate)) return false
        UserUpdate that = (UserUpdate) o
        return properties == that.properties
    }

    @Override
    int hashCode() {
        properties?.hashCode() ?: 0
    }
}

重写后,Spock会自动按对象内容匹配参数,原测试代码无需修改即可通过。

2. 使用Spock闭包匹配参数

如果不想修改实体类,直接在测试中用闭包断言参数的属性值:

then:
1 * trackingCollector.send({ it.properties == mapProperties })

这种方式跳过对象的equals()方法,直接检查目标属性是否匹配,简洁高效。

3. 捕获参数后单独验证

适合需要对参数做复杂验证的场景,先捕获调用时的参数,再逐一验证:

given:
def mapProperties = new JSONObject().put("eligibility", "true").put("group", "group1")
UserUpdate capturedUpdate = null

when:
trackingService.sendUnifiedOnboardingAssignmentUserUpdate()

then:
1 * trackingCollector.send(_) >> { args -> capturedUpdate = args[0] }
and:
capturedUpdate.properties == mapProperties

内容的提问来源于stack exchange,提问作者Eric

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 10:07:49