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

Kotlin runTest测试含delay的非挂起inc()方法不生效问题

核心问题

你当前测试失败的根源有两点:

  • 生产代码中MyViewModel的协程上下文硬编码依赖Dispatchers.IO,测试时仅通过Dispatchers.setMain替换主线程调度器,无法接管IO调度器上运行的协程,测试框架无法控制这部分协程的执行时序。
  • inc()是普通非挂起函数,内部通过launch启动的是异步协程;你在外层调用join()仅能等待inc()方法本身返回,无法等待inc()内部启动的协程执行完成,断言触发时内部的delay(100)逻辑还未执行。
正确实现方案

因为Incrementer接口固定无法修改,我们不需要改动inc()的方法签名,只需要通过调度器依赖注入解决硬编码问题,配合测试调度器的时序控制即可完成测试,这也是JVM/Android协程代码的标准实践。

1. 调整生产代码(不修改接口定义)

将硬编码的Dispatchers.IO改为构造参数传入,生产环境默认使用Dispatchers.IO,测试环境可替换为测试调度器:

interface Incrementer {
    fun inc()
}

class MyViewModel(
    // 调度器作为构造参数注入,生产环境默认用IO调度器
    private val dispatcher: CoroutineDispatcher = Dispatchers.IO
) : Incrementer, CoroutineScope {
    override val coroutineContext: CoroutineContext
        get() = dispatcher

    private val _number = MutableStateFlow(0)
    fun getNumber(): StateFlow<Int> = _number.asStateFlow()

    override fun inc() {
        launch {
            delay(100)
            _number.tryEmit(1)
        }
    }
}

2. 编写测试代码

测试时将runTest内置的测试调度器传入ViewModel,调用inc()后通过advanceUntilIdle()等待测试调度器上所有协程执行完成,再执行断言:

class IncTest {
    @Test
    fun incrementOnce() = runTest {
        // 传入测试调度器,接管ViewModel所有协程的执行
        val viewModel = MyViewModel(dispatcher = StandardTestDispatcher(testScheduler))

        val results = mutableListOf<Int>()
        val resultJob = viewModel.getNumber()
            .onEach(results::add)
            .launchIn(UnconfinedTestDispatcher(testScheduler))

        // 直接调用普通方法inc()
        viewModel.inc()
        // 关键:推进测试时间,直到所有协程任务执行完毕(包含100ms的delay逻辑)
        advanceUntilIdle()

        assertEquals(listOf(0, 1), results)
        resultJob.cancel()
    }
}
注意事项
  • 不需要再全局调用setMain/resetMain,只要所有协程都使用注入的测试调度器,就能完全掌控执行时序,避免全局配置带来的测试交叉污染。
  • 绝对不要在调用inc()后立刻断言:非挂起函数的调用返回仅代表内部协程被提交到调度器队列,不代表协程逻辑执行完成,必须等待调度器空闲后再做校验。
  • 不要额外用launch包裹viewModel.inc()调用再join,这种写法只能等待inc()方法本身返回,无法等待内部异步协程执行,没有实际意义。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.31 12:09:27