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

Kotlin Flow Combine单元测试无法收集全部数据问题排查

问题分析与解决方案

问题原因

你创建job2时使用了独立的CoroutineScope(testDispatcher),这个协程不属于runBlockingTest的作用域,导致advanceUntilIdle()不会等待该协程的任务完成。当第二次调用flow2.emit(true)后,combine的收集逻辑被放到了独立作用域的调度队列中未被执行,因此flow没有发射第二个值,最终collectedValues仅收集到一个元素。

另外,runBlockingTest在Kotlin 1.6+已被标记为废弃,推荐使用更可靠的runTest API。


解决方案

方案1:在runBlockingTest作用域下启动协程

修改job2的创建方式,直接使用当前runBlockingTest的作用域启动协程,确保advanceUntilIdle()能等待其任务完成:

@Test
fun testEmission2() = runBlockingTest {
    val flow = MutableSharedFlow<Boolean>(replay = 1)
    val flow2 = MutableSharedFlow<Boolean>(replay = 1)
    val flow3 = MutableSharedFlow<Boolean>(replay = 1)
    val collectedValues = mutableListOf<Boolean>()
    
    val job: Job = launch {
        flow.collect {
            collectedValues.add(it)
        }
    }

    // 改为在当前runBlockingTest作用域下启动协程
    val job2 = launch(testDispatcher) {
        combine(
            flow2,
            flow3,
            transform = { a, b ->
                Pair(a, b)
            }
        ).collect {
            val (a, b) = it
            flow.emit(a)
        }
    }

    flow3.emit(false)
    flow2.emit(false)
    flow2.emit(true)

    advanceUntilIdle() // 现在会等待job2的任务完成
    job.cancel()
    job2.cancel()

    val expectedValues = listOf(false, true)
    assertEquals(expectedValues, collectedValues)
}

方案2:迁移到runTest API(推荐)

runTest是Kotlin 1.6+官方推荐的测试API,自带TestScope管理协程,无需手动维护TestCoroutineDispatcher:

首先修改父类BaseCoroutineTest,移除过时的调度器配置:

open class BaseCoroutineTest {

    @get:Rule
    val liveDataThreadTestRule = InstantTaskExecutorRule()

    @After
    fun clearMocks() {
        Mockito.framework().clearInlineMocks()
    }
}

然后修改测试函数:

@OptIn(ExperimentalCoroutinesApi::class)
@Test
fun testEmission2() = runTest {
    val flow = MutableSharedFlow<Boolean>(replay = 1)
    val flow2 = MutableSharedFlow<Boolean>(replay = 1)
    val flow3 = MutableSharedFlow<Boolean>(replay = 1)
    val collectedValues = mutableListOf<Boolean>()
    
    val job: Job = launch {
        flow.collect {
            collectedValues.add(it)
        }
    }

    // 直接在runTest的TestScope下启动协程
    val job2 = launch {
        combine(
            flow2,
            flow3,
            transform = { a, b ->
                Pair(a, b)
            }
        ).collect {
            val (a, b) = it
            flow.emit(a)
        }
    }

    flow3.emit(false)
    flow2.emit(false)
    flow2.emit(true)

    advanceUntilIdle()
    job.cancel()
    job2.cancel()

    val expectedValues = listOf(false, true)
    assertEquals(expectedValues, collectedValues)
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 13:52:20