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

Kotlin协程测试中Mockito verify提示方法未调用异常排查

Kotlin协程单元测试报方法未调用问题排查

问题场景

开展Kotlin协程相关单元测试时,运行日志已确认目标方法实际执行,但Mockito校验抛出方法未被调用的错误,无法确认诱因是协程跨线程调度,还是自定义上下文触发onStart的逻辑问题。测试中曾尝试将suspend修饰的fetch()改为普通函数,相同报错仍复现。

报错信息

Wanted but not invoked: testActivity.fetch();
-> at com.example.cws.testActivity.fetch(testActivity.kt:322)

However, there was exactly 1 interaction with this mock:
testActivity.getStateListener$TestApp_release();
-> at com.example.cws.testActivity.getStateListener$TestApp_release(testActivity.kt:113)

关联代码

业务实现代码

val stateListener = object : StateEventListener{
   override fun onStart(testContext: TestEventContext) {
      launch {
         fetch()
      }
   }
}

suspend fun fetch() {
    // 业务逻辑省略
}

原有测试代码

@Test
fun should_fetch_when_started()= runBlocking {
    testActivityController = Robolectric.buildActivity(TestActivity::class.java)
    testActivity = spy(testActivityController
            .create()
            .start()
            .get())
    testActivity.stateListener.onStart()
    verify(testActivity, times(1)).fetch()
}

问题根因

两个核心问题导致报错:

  • Spy代理创建时机错误:原有代码在Activity走完create、start生命周期后才创建spy代理对象,而stateListener是Activity实例初始化阶段就创建的匿名内部类,其闭包持有的是原始Activity实例的引用,并非后续生成的spy代理。日志中看到fetch()执行,是原始Activity实例的方法被调用,该调用不会被spy代理拦截,Mockito自然统计不到目标方法的调用记录,这也是将fetch()改为普通函数仍报错的核心原因。报错信息中只捕获到getStateListener的调用,就是因为访问spy对象的stateListener属性会被代理拦截,但属性返回的内部类实例仍绑定原始Activity对象。
  • 协程作用域不匹配:onStart中调用的launch默认使用Activity自身的协程作用域(通常为lifecycleScope),和测试用的runBlocking作用域完全独立,runBlocking不会等待该独立作用域下的协程执行完成就会走到后续校验逻辑,存在执行时序问题。

修复方案

  • 调整spy创建时机,在Activity实例初始化完成后立刻创建spy代理,再驱动生命周期流程,保证stateListener持有的Activity引用为spy实例。
  • 替换主协程调度器为测试调度器,使用协程测试官方提供的runTest代替runBlocking,等待所有测试协程执行完成后再做校验,避免时序问题。

修复后的测试代码参考:

private val testDispatcher = UnconfinedTestDispatcher()

@Before
fun setUp() {
    // 替换主线程调度器为测试调度器
    Dispatchers.setMain(testDispatcher)
}

@After
fun tearDown() {
    // 测试完成后重置调度器
    Dispatchers.resetMain()
}

@Test
fun should_fetch_when_started() = runTest(testDispatcher) {
    // 先创建Activity实例并生成spy代理
    testActivity = spy(Robolectric.buildActivity(TestActivity::class.java).get())
    // 驱动Activity生命周期
    testActivityController = Robolectric.buildActivity(TestActivity::class.java)
        .create()
        .start()
        .get()
    // 传入mock的上下文参数触发回调
    testActivity.stateListener.onStart(mock(relaxed = true))
    // 等待所有协程任务执行完成
    advanceUntilIdle()
    // 执行校验
    verify(testActivity, times(1)).fetch()
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 10:24:16