Kotlin 状态流在具有视图模型范围的单元测试中不发出

fra*_*yan 10 kotlin kotlin-coroutines kotlin-flow

我对 Kotlin 协程和流程以及它们的单元测试很陌生。我有一个非常简单的测试:

@Test
fun debounce(): Unit = runBlocking {
    val state = MutableStateFlow("hello")
    val debouncedState = state.debounce(500).stateIn(this, SharingStarted.Eagerly, "bla")

    assertThat(debouncedState.value).isEqualTo("bla")

    state.value = "good bye"

    // not yet...
    assertThat(debouncedState.value).isEqualTo("bla")

    delay(600)

    // now!
    assertThat(debouncedState.value).isEqualTo("good bye")

    // cannot close the state flows :(
    cancel("DONE")
}
Run Code Online (Sandbox Code Playgroud)

它工作得很好(除了我无法阻止它,但这是一个不同的问题)。

接下来,我想测试我的ViewModel,其中包括完全相同的状态流。它基本上与上面的代码相同,但我认为它应该在相同的范围内运行,所以viewModel.someMutableStateFlow我尝试运行它viewModelScope

@Test
fun debounce(): Unit = runBlocking {
    val viewModel = MyViewModel()

    // in view model the state and debouncedState are defined the same way as above
    val state = viewModel.someMutableStateFlow
    val debouncedState = state.debounce(500).stateIn(viewModel.viewModelScope, SharingStarted.Eagerly, "bla")

    ///////////////////////////////////////////////////
    // Below is the same code as in previous example //
    ///////////////////////////////////////////////////

    assertThat(debouncedState.value).isEqualTo("bla")

    state.value = "good bye"

    // not yet...
    assertThat(debouncedState.value).isEqualTo("bla")

    delay(600)

    // now!
    assertThat(debouncedState.value).isEqualTo("good bye")

    // cannot close the state flows :(
    cancel("DONE")
}
Run Code Online (Sandbox Code Playgroud)

但这一次debouncedState.value从未改变,它bla一直保持不变!这些状态不会发出任何东西。

这是否与我正在使用viewModelScope并且可能没有运行有关?

关于这里发生的事情的一些解释会很棒。

ali*_*ter 1

这个关于设置主调度程序的文档中:

但是,某些 API(例如 viewModelScope)在底层使用硬编码的 Main 调度程序。

然后描述如何用 TestDispatcher 替换 Main 调度程序:

class HomeViewModelTest {
    @Test
    fun settingMainDispatcher() = runTest {
        val testDispatcher = UnconfinedTestDispatcher(testScheduler)
        Dispatchers.setMain(testDispatcher)

        try {
            val viewModel = HomeViewModel()
            viewModel.loadMessage() // Uses testDispatcher, runs its coroutine eagerly
            assertEquals("Greetings!", viewModel.message.value)
        } finally {
            Dispatchers.resetMain()
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

引用这篇文章:

如果 Main 调度程序已替换为 TestDispatcher,则任何新创建的 TestDispatcher 将自动使用 Main 调度程序中的调度程序,包括由 runTest 创建的 StandardTestDispatcher(如果没有其他调度程序传递给它)。

该文档还描述了如何制定测试规则,因此您不必在每个测试中都执行此操作。