待测试代码
fun updateAccount() {
coroutineScope.launch {
startLoading()
val isAccountUpdated = updateAccountUseCase()
if (isAccountUpdated) {
navigator.navigateUp()
} else {
completeLoading()
// TODO: Show Error
}
}
}
上下文
val refreshSignal: MutableSharedFlow<Unit> = MutableSharedFlow(
replay = 0,
extraBufferCapacity = 1,
)
fun startLoading() {
isLoading = true
refreshSignal.tryEmit(Unit)
}
fun completeLoading() {
isLoading = false
refreshSignal.tryEmit(Unit)
}
测试成功
@Test
fun `updateAccount when updateAccountUseCase returns true`() = testScope.runTest {
whenever(updateAccountUseCase()).thenReturn(true)
SUT.refreshSignal.test {
SUT.updateAccount()
assertEquals(Unit, awaitItem())
assertEquals(true, SUT.isLoading)
verify(navigator).navigateUp()
expectNoEvents()
}
}
测试失败
@Test
fun `updateAccount when updateAccountUseCase returns false`() = testScope.runTest {
whenever(updateAccountUseCase()).thenReturn(false)
SUT.refreshSignal.test {
SUT.updateAccount()
assertEquals(Unit, awaitItem())
assertEquals(true, SUT.isLoading) // This assertion fails as the isLoading is reset by this time
assertEquals(Unit, awaitItem())
assertEquals(false, SUT.isLoading)
expectNoEvents()
}
}
有关调试尝试的其他信息,
yield()
和 delays()
。yield()
不起作用。
使用时
delay()
,
如果产品代码被这样修改,
fun updateAccount() {
coroutineScope.launch {
startLoading()
delay(100) // Added delay here
val isAccountUpdated = updateAccountUseCase()
if (isAccountUpdated) {
navigator.navigateUp()
} else {
completeLoading()
// TODO: Show Error
}
}
}
测试方法修改如下,
@Test
fun `updateAccount when updateAccountUseCase returns true`() = testScope.runTest {
whenever(updateAccountUseCase()).thenReturn(true)
SUT.refreshSignal.test {
SUT.updateAccount()
assertEquals(Unit, awaitItem())
assertEquals(true, SUT.isLoading)
advanceUntilIdle() // Added this advance
expectNoEvents()
}
verify(navigator).navigateUp()
}
@Test
fun `updateAccount when updateAccountUseCase returns false`() = testScope.runTest {
whenever(updateAccountUseCase()).thenReturn(false)
SUT.refreshSignal.test {
SUT.updateAccount()
assertEquals(Unit, awaitItem())
assertEquals(true, SUT.isLoading)
advanceUntilIdle() // Added this advance
assertEquals(Unit, awaitItem())
assertEquals(false, SUT.isLoading)
expectNoEvents()
}
}
我不确定我是否正确理解了您的示例,但感觉您希望生产者以某种方式等待消费者,因此只有在我们从流程中消费完一个项目后,
isLoading
值才会发生变化。实际上,生产者和消费者是同时运行的。在第二个示例中,生产者调用 startLoading
,然后几乎立即调用 completeLoading
。当消费者获得第一个项目时,生产者已经完成了这两个功能。但这个执行顺序可能无法保证 - 这是生产者和消费者之间的竞争条件。
使用流的同时共享可变状态感觉很奇怪(
isLoading
)。使用流等框架的要点之一是避免共享可变状态,因为这很容易出错并导致上述问题。
典型的解决方案是使用流发出布尔值。我没有提供完整的示例,因为您可能知道此类解决方案,并且出于某种原因您决定不使用它。