ago*_*st_ 17 android kotlin android-viewmodel kotlin-coroutines kotlin-flow
最近作为 Kotlin 协程的一部分引入了该类StateFlow
。
我目前正在尝试它并在尝试对我的 ViewModel 进行单元测试时遇到问题。我想要实现的目标:测试我StateFlow
是否在 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 }
}
}
Run Code Online (Sandbox Code Playgroud)
我的测试:
@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
}
Run Code Online (Sandbox Code Playgroud)
访问发出的最后一个值有效。但我想测试的是,所有发出的值都以正确的顺序发出。使用这段代码: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)
....
Run Code Online (Sandbox Code Playgroud)
我想我错过了一些明显的东西。但不知道为什么,因为我刚刚开始使用 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()
}
}
Run Code Online (Sandbox Code Playgroud)
Mah*_*zad 10
我从Kotlin 协程 GitHub 存储库中的解决方案中得出的另一种方法:
@Test fun `The StateFlow should emit all expected values`() = runTest {
val dispatcher = UnconfinedTestDispatcher(testScheduler)
val viewModel = MyViewModel(dispatcher)
val results = mutableListOf<Int>()
backgroundscope.launch(dispatcher) { viewModel.number.toList(results) }
viewModel.updateNumberTo(5)
viewModel.updateNumberTo(8)
assertThat(results).isEqualTo(listOf(1, 5, 8))
}
Run Code Online (Sandbox Code Playgroud)
这是我的ViewModel类:
class MyViewModel(private val dispatcher: CoroutineDispatcher) : ViewModel() {
private val _number = MutableStateFlow(1)
val number = _number.asStateFlow()
fun updateNumberTo(newNumber: Int) {
_number.value = newNumber
// OR to update asynchronously:
// viewModelScope.launch(dispatcher) {
// // Do some work...
// _number.value = newNumber
// }
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,我使用的是 Kotlin 1.6.10和 kotlinx.coroutines-test 1.6.1:
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.1")
Run Code Online (Sandbox Code Playgroud)
观看这个必看的 KotlinConf'23 视频,解释与测试协程相关的所有内容。
另请参阅新测试 API 的官方 Kotlin 协程迁移指南。
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()
}
Run Code Online (Sandbox Code Playgroud)
您的具体用例:
@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()
}
Run Code Online (Sandbox Code Playgroud)
两个重要的事情:
java.lang.IllegalStateException: This job has not completed yet
toList
)时,您会收到最后一个状态。但是,如果您首先开始收集并在调用函数之后viewModel.getUserWallets()
,那么在result
列表中,您将拥有所有状态,以防您也想对其进行测试。 归档时间: |
|
查看次数: |
5356 次 |
最近记录: |