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

如何在Spock中Mock Kotlin高阶函数?

Spock测试Kotlin高阶函数的函数引用匹配问题

问题场景

在Kotlin代码中,someOtherClass::foo是KFunction0<Unit>类型,但在Groovy编写的Spock测试里,同样的语法会生成Groovy闭包,导致调用验证时类型不匹配,触发断言错误。

Kotlin业务代码

import jakarta.inject.Singleton

@Singleton
class SomeClass {
  fun bar(function: () -> Unit) {
    function()
  }
}

@Singleton
class SomeOtherClass {
  fun foo() {
    println("foo")
  }
}

@Singleton
class ClassUnderTest(
  private val someClass: SomeClass,
  private val someOtherClass: SomeOtherClass,
) {
  fun test() {
    // someOtherClass::foo 的类型为 KFunction0<Unit>
    someClass.bar(someOtherClass::foo)
  }
}

Spock测试代码

class ClassUnderTestSpecification extends Specification {

  def someClass = Mock(SomeClass)
  def someOtherClass = Mock(SomeOtherClass)
  def classUnderTest = new ClassUnderTest(someClass, someOtherClass)

  def 'test class'() {
    when:
    classUnderTest.test()

    then:
    // someOtherClass::foo 的类型为 Groovy Closure
    // 验证失败,它应该是Kotlin的KFunction
    1 * someClass.bar(someOtherClass::foo)
    0 * _
  }
}

触发的错误信息

Too many invocations for:

0 * _   (1 invocation)

Matching invocations (ordered by last occurrence):

1 * someClass.bar(fun com.example.package.SomeOtherClass.foo(): kotlin.Unit)   <-- this triggered the error

解决方案

方案1:使用类型匹配器

不精确匹配函数引用实例,而是匹配其类型,适合只需要验证调用次数和参数类型的场景:

then:
1 * someClass.bar(_ instanceOf kotlin.jvm.functions.Function0)
0 * _

方案2:验证函数引用的属性

如果需要确认是指定函数的引用,可以检查函数名称等属性,避免实例不匹配的问题:

then:
1 * someClass.bar({ it.name == "foo" })
0 * _

方案3:在Kotlin中封装函数引用获取

在SomeOtherClass中添加方法返回绑定到实例的函数引用,统一业务代码和测试代码的获取方式:
修改Kotlin的SomeOtherClass:

@Singleton
class SomeOtherClass {
  fun foo() {
    println("foo")
  }

  fun getFooFunction(): () -> Unit = this::foo
}

修改ClassUnderTest的test方法:

fun test() {
  someClass.bar(someOtherClass.getFooFunction())
}

测试代码中直接调用封装方法匹配:

then:
1 * someClass.bar(someOtherClass.getFooFunction())
0 * _

方案4:使用Kotlin类级函数引用(适用于无状态函数)

如果foo是无状态的,可以在Kotlin代码中改用类级函数引用,测试中同样使用类级引用匹配(因为类级函数引用在JVM上是单例):
修改ClassUnderTest的test方法:

fun test() {
  someClass.bar(SomeOtherClass::foo)
}

测试代码中匹配类级引用:

then:
1 * someClass.bar(SomeOtherClass::foo)
0 * _

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 22:23:23