最近上课 StateFlow
was introduced作为 Kotlin 协程的一部分。
我目前正在尝试它并在尝试对我的 ViewModel 进行单元测试时遇到问题。我想要实现的目标:测试我的 StateFlow 是否在我的 ViewModel 中以正确的顺序接收所有状态值。
我的代码如下。
View 模型:
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)
我收到以下错误: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)
....
我想我错过了一些明显的东西。但不知道为什么当我刚刚开始使用协程和 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()
}
}
最佳答案
SharedFlow/StateFlow 是一个热流,如文档中所述,A shared flow is called hot because its active instance exists independently of the presence of collectors.
这意味着,启动流程集合的范围不会自行完成。
要解决此问题,您需要取消调用 collect 的范围,并且由于您的测试范围是测试本身,因此无法取消测试,因此您需要在不同的作业中启动它。
@Test
fun `Testing a integer state flow`() = runBlockingTest{
val _intSharedFlow = MutableStateFlow(0)
val intSharedFlow = _intSharedFlow.asStateFlow()
val testResults = mutableListOf<Int>()
val job = launch {
intSharedFlow.toList(testResults)
}
_intSharedFlow.value = 5
assertEquals(2, testResults.size)
assertEquals(0, testResults.first())
assertEquals(5, testResults.last())
job.cancel()
}
您的具体用例:@Test
fun `observe user wallets ok`() = runBlockingTest {
whenever(api.getAssetWallets()).thenReturn(TestUtils.getAssetsWalletResponseOk())
whenever(api.getFiatWallets()).thenReturn(TestUtils.getFiatWalletResponseOk())
viewModel.getUserWallets()
val result = arrayListOf<State<UserWallets>>()
val job = launch {
viewModel.userWallet.toList(result) //now it should work
}
Assertions.assertThat(viewModel.userWallet.value is State.Success).isTrue() //works, last value enmited
Assertions.assertThat(result.first() is State.Success) //also works
job.cancel()
}
两个重要的事情:java.lang.IllegalStateException: This job has not completed yet
toList
内)时,您会收到最后一个状态。但是如果你第一次开始收集,然后你调用你的函数 viewModel.getUserWallets()
,然后在 result
内列表,您将拥有所有状态,以防您也想对其进行测试。 关于android - 单元测试新的 Kotlin 协程 StateFlow,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62110761/