如何使用来自init{}的API调用测试ViewModel+Flow



我有ViewModel,它将流公开到片段。我从ViewModel的init调用API,它发出不同的状态。我无法编写单元测试来检查所有发出的状态。

我的ViewModel

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()
}
}

现在,我想测试这个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())
}
}
}

如何在.test{}块上将init延迟到内部。已尝试创建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>
} 

然后测试可以是这样的:

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)
}
}

附加测试依赖项:

testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.1'
testImplementation "android.arch.core:core-testing:1.1.1"

最新更新