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

如何对包含多个async调用的Kotlin协程进行单元测试

问题根因

你遇到的返回值不符合预期的问题由两个核心原因导致:

  • MockK的单slot捕获器不是线程安全的:slot内部仅用单个字段存储捕获到的参数,当两个协程在Dispatchers.Default线程池并发执行时,第二个协程调用dependency1.func1时会覆盖第一个协程存入slot的参数值。你在answers代码块中读取slot1.captured时,拿到的不一定是当前调用对应的入参,自然会返回错误的结果。
  • 待测试代码硬编码了Dispatchers.Default协程调度器:这种写法既破坏了代码的可测试性,也让单元测试无法控制协程的执行时序,容易引入偶发的并发时序问题。
修正步骤

1. 重构待测试类,注入协程调度器

不要在业务代码中硬编码协程调度器,将其作为构造参数传入(生产环境可以给默认值,不影响原有业务逻辑),方便测试时替换调度器消除并发干扰:

class ClassToTest(
    val dependency1: Dependency1,
    val dependency2: Dependency2,
    // 生产环境默认使用Dispatchers.Default,测试时可替换
    private val ioDispatcher: CoroutineDispatcher = Dispatchers.Default
) {
    fun funToTest(list: List<X>) {
        runBlocking {
            val deferreds: List<Deferred<Type>> = list.map { item ->
                // 替换硬编码的调度器为构造传入的实例
                async(ioDispatcher) {
                    val v1 = dependency1.func1(item)
                    val v2 = dependency2.func2(v1)
                    someFunc(v2)
                }
            }
            val results = deferreds.awaitAll()
        }
    }
}

2. 修正Mock逻辑,弃用单slot做入参判断

你不需要用slot捕获入参来做返回值分支判断,MockK的answers作用域本身就可以直接获取当前调用的入参,这个入参是和单次调用绑定的,完全不会被其他并发调用覆盖:

// 移除原有slot定义,直接在answers中获取当前调用的入参
every { dependency1.func1(any()) } answers {
    // firstArg<X>() 代表当前这次func1调用传入的第一个参数,即对应的item
    val currentItem = firstArg<X>()
    if (currentItem.field == xxx) {
        return1
    } else {
        return2
    }
}

// dependency2的Mock逻辑和上述写法一致,直接从answers作用域取当前调用入参判断即可

如果后续你确实需要校验所有调用的入参,不要用单个slot,改用可变列表存储每次捕获的参数即可:

val capturedParams = mutableListOf<X>()
every { dependency1.func1(capture(capturedParams)) } answers {
    val currentItem = firstArg<X>()
    // 分支判断逻辑同上
}
// 测试执行完成后可以遍历capturedParams做参数校验

3. 测试时替换调度器消除并发干扰

初始化待测试类时,传入Dispatchers.Unconfined或者kotlinx-coroutines-test提供的测试调度器,让协程在当前测试线程同步执行,彻底避免并发时序导致的偶发问题:

val testedClass = ClassToTest(
    dependency1 = mockDependency1,
    dependency2 = mockDependency2,
    // 测试时用无约束调度器,协程同步执行
    ioDispatcher = Dispatchers.Unconfined
)

// 正常调用待测试方法即可
testedClass.funToTest(testListOf2Items)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 02:12:06