对新的Kotlin协程StateFlow进行单元测试

问题描述 投票:0回答:1

最近是introducedStateFlow,作为Kotlin协程的一部分。

我目前正在尝试对单元进行ViewModel单元测试时遇到问题。我要实现的目标是:测试我的StateFlow是否在ViewModel中以正确的顺序接收了所有状态值。

我的代码如下:

ViewModel:

class WalletViewModel(private val getUserWallets: GetUersWallets) : ViewModel() {

val userWallet: StateFlow<State<UserWallets>> get() = _userWallets
private val _userWallets: MutableStateFlow<State<UserWallets>> =
    MutableStateFlow(State.Init)

fun getUserWallets() {
    viewModelScope.launch {
        getUserWallets.getUserWallets()
            .onStart { _userWallets.value = State.Loading }
            .collect { _userWallets.value = it }
    }
}

我的测试:

    @Test
fun `observe user wallets ok`() = runBlockingTest {
    Mockito.`when`(api.getAssetWallets()).thenReturn(TestUtils.getAssetsWalletResponseOk())
    Mockito.`when`(api.getFiatWallets()).thenReturn(TestUtils.getFiatWalletResponseOk())

    viewModel.getUserWallets()

    val res = arrayListOf<State<UserWallets>>()
    viewModel.userWallet.toList(res) //doesn't works

    Assertions.assertThat(viewModel.userWallet.value is State.Success).isTrue() //works, last value enmited
}

访问最后发出的值有效。但是我要测试的是所有发出的值都以正确的顺序发出。这段代码:viewModel.userWallet.toList(res) //doesn't works我收到以下错误:

java.lang.IllegalStateException: This job has not completed yet
    at kotlinx.coroutines.JobSupport.getCompletionExceptionOrNull(JobSupport.kt:1189)
    at kotlinx.coroutines.test.TestBuildersKt.runBlockingTest(TestBuilders.kt:53)
    at kotlinx.coroutines.test.TestBuildersKt.runBlockingTest$default(TestBuilders.kt:45)
    at WalletViewModelTest.observe user wallets ok(WalletViewModelTest.kt:52)
....

我想我缺少明显的东西。但不知道为什么,因为我刚开始使用Coroutine和Flow,并且在不使用已经使用过的runBlockingTest时似乎发生了此错误。

编辑:作为临时解决方案,我将其作为实时数据进行测试:

    @Captor
    lateinit var captor: ArgumentCaptor<State<UserWallets>>

    @Mock
    lateinit var walletsObserver: Observer<State<UserWallets>>

    @Test
    fun `observe user wallets ok`() = runBlockingTest {
        viewModel.userWallet.asLiveData().observeForever(walletsObserver)

        viewModel.getUserWallets()

        captor.run {
            Mockito.verify(walletsObserver, Mockito.times(3)).onChanged(capture())
            Assertions.assertThat(allValues[0] is State.Init).isTrue()
            Assertions.assertThat(allValues[1] is State.Loading).isTrue()
            Assertions.assertThat(allValues[2] is State.Success).isTrue()
        }
    }
android kotlin kotlin-coroutines android-viewmodel kotlin-flow
1个回答
0
投票

runBlockingTest只是跳过您的情况下的延迟,但不会覆盖测试调度程序在ViewModel中使用的调度程序。您需要将TestCoroutineDispatcher注入到ViewModel中,或者由于您正在使用的viewModelScope.launch {}默认情况下已经使用Dispatchers.Main,因此您需要通过Dispatchers.setMain(testCoroutineDispatcher)覆盖主调度程序。您可以创建以下规则并将其添加到测试文件。

class MainCoroutineRule(
        val testDispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()
) : TestWatcher() {

    override fun starting(description: Description?) {
        super.starting(description)
        Dispatchers.setMain(testDispatcher)
    }

    override fun finished(description: Description?) {
        super.finished(description)
        Dispatchers.resetMain()
        testDispatcher.cleanupTestCoroutines()
    }
} 

并且在您的测试文件中

@get:Rule
var mainCoroutineRule = MainCoroutineRule()

@Test
fun `observe user wallets ok`() = mainCoroutineRule.testDispatcher.runBlockingTest {
}

Btw注入调度程序始终是一个好习惯。例如,如果您在协程范围中使用了Dispatchers.Main以外的其他分派器,例如viewModelScope.launch(Dispatchers.Default),则即使使用测试分派器,测试也将再次失败。原因是您只能用Dispatchers.setMain()覆盖主调度程序,因为从名称可以理解它,但不能覆盖Dispatchers.IODispatchers.Default。在这种情况下,您需要将mainCoroutineRule.testDispatcher注入视图模型并使用注入的调度程序,而不是对其进行硬编码。

© www.soinside.com 2019 - 2024. All rights reserved.