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

如何使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 01:37:03