如何在Kotlin中强制模拟静态扩展方法的返回值?
问题原因
你遇到的核心问题是Kotlin扩展函数的本质是静态方法,Mockito常规的对象mock方式无法拦截静态方法调用:
- 你定义的
Job.toOtherJob()扩展函数,编译成字节码后其实是Mapper类中的静态方法toOtherJob(Job),只是Kotlin语法糖让它看起来像是Job的成员方法。 - 你mock的是
Job对象,但扩展函数不属于Job的成员,所以whenever(job.toOtherJob())本质是调用静态方法,Mockito无法拦截,自然会执行真实逻辑。
解决方案
方案1:修改源码(推荐)
把扩展函数改成可依赖注入的接口方法,彻底规避静态方法的测试问题:
- 定义Mapper接口:
interface JobMapper { fun Job.toOtherJob(): OtherJob.Job }
- 实现默认Mapper:
class DefaultJobMapper : JobMapper { override fun Job.toOtherJob(): OtherJob.Job { // 原来的转换逻辑 } }
- 业务代码中注入
JobMapper接口,测试时mock这个接口:
class ServiceTests { @Mock private lateinit var jobMapper: JobMapper @Mock private lateinit var job: Job @BeforeEach fun setUp() { MockitoAnnotations.openMocks(this) } @Test fun testJob() { val mockOtherJob = OtherJob.Job() // mock接口方法 whenever(jobMapper.toOtherJob(job)).thenReturn(mockOtherJob) // 业务代码中通过jobMapper调用转换 val result = jobMapper.toOtherJob(job) assertEquals(mockOtherJob, result) } }
方案2:静态Mock(不推荐,仅当无法修改源码时用)
使用Mockito的静态mock功能(需要Mockito 3.4.0+,并引入mockito-inline依赖):
- 确保依赖中包含
mockito-inline(Gradle示例):
testImplementation "org.mockito:mockito-inline:4.+"
- 修改测试代码,静态mock
Mapper类:
class ServiceTests { @Mock private lateinit var job: Job @BeforeEach fun setUp() { MockitoAnnotations.openMocks(this) } @Test fun testJob() { val mockOtherJob = OtherJob.Job() // 静态mock Mapper类,作用域在try-with-resources内 mockStatic(Mapper::class.java).use { mockedMapper -> // 指定静态方法的返回值 mockedMapper.`when`<OtherJob.Job> { job.toOtherJob() } .thenReturn(mockOtherJob) // 调用转换方法,此时会返回mock值 val result = job.toOtherJob() assertEquals(mockOtherJob, result) } } }
测试代码的额外优化点
- 把
@BeforeAll/@AfterAll改成@BeforeEach/@AfterEach,因为@BeforeAll是静态方法,不适合初始化非静态的mock对象 - 不要把mock对象声明为
nullable(val job: Job? = null),Mockito会生成非空的代理对象,直接用lateinit var job: Job即可
内容的提问来源于stack exchange,提问作者AzzamAziz
相关产品推荐
相关产品推荐

