如何使用Mockito正确验证Kotlin中的扩展函数调用?
如何用Mockito正确验证Kotlin扩展函数的调用?
问题场景
当用Mockito验证Kotlin扩展函数的调用时,会出现Mockito统计函数内部属性访问而非扩展函数本身调用的问题。例如以下测试代码:
import org.junit.jupiter.api.Test import org.mockito.kotlin.spy import org.mockito.kotlin.times import org.mockito.kotlin.verify class MockitoTest { class Amount(val value: Int) fun Amount.validate() { require(this.value > 0) { "Amount should be greater than 0" } } @Test fun `spy the validate calls`() { // given val spyAmount = spy(Amount(1)) // when spyAmount.validate() val value = spyAmount.value // verify verify(spyAmount, times(1)).validate() } }
运行后会抛出错误:
amount.getValue(); Wanted 1 time: -> at MockitoTest$Amount.getValue(MockitoTest.kt:7) But was 2 times: -> at MockitoTest.validate(MockitoTest.kt:10) -> at MockitoTest.spy the validate calls(MockitoTest.kt:21)
而如果把validate()改成Amount类的成员函数,测试就能正常通过。
原因分析
Kotlin的扩展函数本质上是静态工具函数,编译器会把它转换成接收Amount实例作为第一个参数的静态方法。当你调用spyAmount.validate()时,实际上是调用了这个静态函数,传入spyAmount作为参数——Mockito的spy只能追踪Amount实例自身的成员方法和属性访问,无法识别这个外部静态函数的调用,所以verify(spyAmount).validate()会被错误地解析为验证Amount类中不存在的成员方法,最终误触发了对value属性getValue()方法的验证。
解决方案
方案1:将扩展函数转为类成员或接口默认方法
把扩展函数移到Amount类内部,或者定义一个包含该方法的接口并让Amount实现,这样validate()就成为实例的成员方法,Mockito可以正常追踪:
interface Validatable { fun validate() } class Amount(val value: Int) : Validatable { override fun validate() { require(value > 0) { "Amount should be greater than 0" } } } @Test fun `spy the validate calls`() { val spyAmount = spy(Amount(1)) spyAmount.validate() val value = spyAmount.value verify(spyAmount, times(1)).validate() }
方案2:将扩展函数封装到工具类,spy工具类实例
如果不想修改Amount类,可以把扩展函数移到一个工具对象中,然后spy这个工具对象,验证其方法调用:
object AmountValidator { fun Amount.validate() { require(this.value > 0) { "Amount should be greater than 0" } } } @Test fun `verify extension function call`() { val spyValidator = spy(AmountValidator) val amount = Amount(1) spyValidator.validate(amount) val value = amount.value verify(spyValidator, times(1)).validate(amount) }
方案3:使用MockK替代Mockito
MockK是专门为Kotlin设计的测试框架,对扩展函数、高阶函数等Kotlin特性支持更友好,可以直接验证扩展函数的调用:
import io.mockk.every import io.mockk.mockk import io.mockk.verify import org.junit.jupiter.api.Test class MockKTest { class Amount(val value: Int) fun Amount.validate() { require(this.value > 0) { "Amount should be greater than 0" } } @Test fun `verify extension function call`() { val amount = mockk<Amount>() every { amount.value } returns 1 amount.validate() verify(exactly = 1) { amount.validate() } } }
内容的提问来源于stack exchange,提问作者Mohammad Esteki
相关产品推荐
相关产品推荐

