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

如何正确对只读SharedFlow进行单元测试?

问题:SharedFlow测试无法获取发射值的排查与解决

问题描述

测试仓库类中的SharedFlow时遇到异常:实际场景是单例类订阅多个Flow,经处理后通过shareIn()作为应用内单一数据源,但测试时无法获取第一个发射值——要么出现协程未完成的问题,要么toList()返回空列表。同时希望保持Flow的只读特性,不直接从测试中发射值,仅验证其自身逻辑(如合并Flow、API调用等)。

简化后的代码如下:

仓库类代码

import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.*

class Repo(dispatcher: CoroutineDispatcher) {

    val flow1 = MutableStateFlow(0)
    val flow2 =  MutableStateFlow(1)

    val sharedFlow = flow1.flatMapLatest { zero ->
        flow2.map { one -> zero to one }
    }.transformLatest { (zero, one) ->
        emit(zero + one)
    }.shareIn(CoroutineScope(dispatcher), SharingStarted.Eagerly, 1)
}

测试类代码

import junit.framework.Assert.assertEquals
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runTest
import org.junit.Test

class TestClass {

    @Test
    fun `checking the right state for the shared flow in repo`() = runTest {
        val repo = Repo(testDispatcher)

        launch {
            val state = repo.sharedFlow.firstOrNull()
            assertEquals(state, 1) // 0 + 1
        }.join()
    }
}

错误原因与解决办法

1. shareIn的CoroutineScope生命周期不匹配

你在shareIn中传入的是CoroutineScope(dispatcher),这个独立Scope没有和测试的runTest作用域绑定。测试代码执行时,该Scope可能还没来得及启动收集上游Flow,测试就已结束,导致无法收集到值。

解决办法:
修改Repo类,让其依赖外部传入的CoroutineScope而非CoroutineDispatcher,确保SharedFlow的收集生命周期和测试作用域一致:

class Repo(private val scope: CoroutineScope) {

    val flow1 = MutableStateFlow(0)
    val flow2 =  MutableStateFlow(1)

    val sharedFlow = flow1.flatMapLatest { zero ->
        flow2.map { one -> zero to one }
    }.transformLatest { (zero, one) ->
        emit(zero + one)
    }.shareIn(scope, SharingStarted.Eagerly, 1)
}

2. 测试代码冗余的协程调用

测试中额外launch协程再join的操作完全多余,直接在runTest的作用域内调用first()即可,避免协程调度带来的不确定性。

最终修正后的测试代码

import junit.framework.Assert.assertEquals
import kotlinx.coroutines.test.runTest
import org.junit.Test

class TestClass {

    @Test
    fun `checking the right state for the shared flow in repo`() = runTest {
        val repo = Repo(this) // 传入runTest的CoroutineScope
        val state = repo.sharedFlow.first()
        assertEquals(state, 1)
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 22:40:24