DCo*_*der 5 android turbine kotlin kotlintest kotlin-coroutines
我有 ViewModel 将流程暴露给片段。我从 ViewModel 的 init 调用 API,它会发出不同的状态。我无法编写单元测试来检查所有发出的状态。
我的视图模型
class FooViewModel constructor( fooProvider : FooProvider){
private val _uiState = MutableSharedFlow<UiState>(replay = 1)
// Used in fragment to collect ui states.
val uiState = _uiState.asSharedFlow()
init{
_uiState.emit(FetchingFoo)
viewModelScope.runCatching {
// Fetch shareable link from server [users.sharedInvites.list].
fooProvider.fetchFoo().await()
}.fold(
onSuccess = {
_uiState.emit(FoundFoo)
},
onFailure = {
_uiState.emit(EmptyFoo)
}
)
}
sealed class UiState {
object FetchingFoo : UiState()
object FoundFoo : UiState()
object EmptyFoo : UiState()
}
}
Run Code Online (Sandbox Code Playgroud)
现在我想测试这个 ViewModel 以检查是否发出了所有状态。
我的测试:注意我正在使用涡轮机库。
class FooViewModelTest{
@Mock
private lateinit var fooProvider : FooProvider
@Test
fun testFooFetch() = runTest {
whenever(fooProvider.fetchFoo()).thenReturn(// Expected API response)
val fooViewModel = FooViewModel(fooProvider)
// Here lies the problem. as we create fooViewModel object API is called.
// before reaching test block.
fooViewModel.uiState.test{
// This condition fails as fooViewModel.uiState is now at FoundFoo.
assertEquals(FetchingFoo, awaitItem())
assertEquals(FoundFoo, awaitItem())
}
}
}
Run Code Online (Sandbox Code Playgroud)
如何延迟init到.test{}街区内。尝试创建 ViewModel 对象by Lazy{}但不起作用。
为了测试而“延迟”发射并不是很务实,这可能会产生不稳定的测试。
这更多的是一个编码问题 - 正确的问题应该是“这个逻辑是否属于类初始化。它更难以测试这一事实应该会给您暗示它不太理想。
更好的解决方案是使用StateFlow延迟初始化的类似(为了测试而假设的一些代码):
class FooViewModel constructor(private val fooProvider: FooProvider) : ViewModel() {
val uiState: StateFlow<UiState> by lazy {
flow<UiState> {
emit(FoundFoo(fooProvider.fetchFoo()))
}.catch { emit(EmptyFoo) }
.flowOn(Dispatchers.IO)
.stateIn(
scope = viewModelScope,
started = WhileSubscribed(5_000),
initialValue = FetchingFoo)
}
sealed class UiState {
object FetchingFoo : UiState()
data class FoundFoo(val list: List<Any>) : UiState()
object EmptyFoo : UiState()
}
}
fun interface FooProvider {
suspend fun fetchFoo(): List<Any>
}
Run Code Online (Sandbox Code Playgroud)
那么测试可能是这样的:
class FooViewModelTest {
@ExperimentalCoroutinesApi
@Test fun `whenObservingUiState thenCorrectStatesObserved`() = runTest {
val states = mutableListOf<UiState>()
FooViewModel { emptyList() }
.uiState
.take(2)
.toList(states)
assertEquals(2, states.size)
assertEquals(listOf(FetchingFoo, FoundFoo(emptyList()), states)
}
@ExperimentalCoroutinesApi
@Test fun `whenObservingUiStateAndErrorOccurs thenCorrectStatesObserved`() = runTest {
val states = mutableListOf<UiState>()
FooViewModel { throw IllegalStateException() }
.uiState
.take(2)
.toList(states)
assertEquals(2, states.size)
assertEquals(listOf(FetchingFoo, EmptFoo), states)
}
}
Run Code Online (Sandbox Code Playgroud)
额外的测试依赖项:
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.1'
testImplementation "android.arch.core:core-testing:1.1.1"
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
3887 次 |
| 最近记录: |