渐进式编程课
KMP 目录
第 46 课2026.09.03

用确定的调度步骤验证晚订阅者不会收到 replay=0 的历史事件。

第 46 课:SharedFlow订阅时机测试

  • 日期:2026-09-03
  • 课程序号:第 46 课
  • 知识点:runTest、runCurrent 与无重放事件

用途

用确定的调度步骤验证晚订阅者不会收到 replay=0 的历史事件。

核心概念

  • 默认 MutableSharedFlow 没有重放缓存,无订阅者时发出的值不会为未来订阅者保存。
  • runTest 中 launch 默认不会立刻执行;runCurrent 先运行当前时刻的待执行任务,让订阅建立。
  • 用有限的 take(1) 结束收集,避免测试永远等待热流完成。

最小代码或操作示例

kotlin
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest

@OptIn(ExperimentalCoroutinesApi::class)
class EventTimingTest {
    @Test
    fun lateSubscriberMissesOldEvent() = runTest {
        val events = MutableSharedFlow<String>(replay = 0)
        events.emit("old")
        val received = mutableListOf<String>()
        val job = launch { events.take(1).toList(received) }
        runCurrent()
        events.emit("new")
        job.join()
        assertEquals(listOf("new"), received)
    }
}

3~5 分钟练习

在 commonTest 中沿用 kotlin.test 与 kotlinx-coroutines-test 依赖(协程依赖版本保持一致)。将 replay 改为 1,重新运行并解释结果;随后改正断言。

参考答案

kotlin
// replay=1 时,runCurrent 会使订阅者收到 old,take(1) 随即结束。
// 后发的 new 不再由这个已完成的订阅者收集。
assertEquals(listOf("old"), received)

与上一课的联系

上一课区分状态和事件语义;本课用可重复测试把“无订阅时丢弃”和“重放历史”变成可观察证据。

参考资料