Berkeli Alashov
06/27/2022, 6:11 AMBerkeli Alashov
06/27/2022, 6:12 AMclass StateFlowTestViewModel : ViewModel() {
data class State(val data: Boolean? = null, val isSaving: Boolean = false, val error: Throwable? = null) {
val isSaveEnabled = data != null && !isSaving
companion object {
val Empty = State()
}
}
@VisibleForTesting val dataState = MutableStateFlow<Boolean?>(null)
@VisibleForTesting val isSavingState = MutableStateFlow<Boolean>(false)
@VisibleForTesting val errorState = MutableStateFlow<Throwable?>(null)
val state = combine(dataState, isSavingState, errorState, ::State).stateIn(viewModelScope, SharingStarted.Lazily, State())
fun setData(data: Boolean?) {
dataState.value = data
// also tried .update { data}, .emit, .tryEmit with viewModelScope.launch
}
fun save() = viewModelScope.launch {
with(state.value) {
runCatching {
require(isSaveEnabled) { "Save is not enabled, is UI out of sync?" }
}.onFailure {
errorState.value = it
return@launch
}
isSavingState.value = true
// delay(100)
isSavingState.value = false
errorState.value = Throwable("TestError")
}
}
}
@RunWith(AndroidJUnit4::class)
class StateFlowTestViewModelTest {
@get:Rule
val instantTaskExecutorRule = InstantTaskExecutorRule()
@Test
fun test_setDataAndSave() = runTest {
val viewModel = StateFlowTestViewModel()
viewModel.setData(true)
assertThat(viewModel.dataState.value)
.isTrue() // doesn't fail
// assertThat(viewModel.state.value.data)
// .isTrue() // fails
assertThat(viewModel.state.first().data)
.isTrue() // doesn't fail.
// ^ calling viewModel.state.first() is also somehow required for below viewModel.save call
// so it doesn't fail with ui out of sync error (bcz viewModel.state.value.data is still null & viewModel.state.value.isSaveEnabled is false)
viewModel.save()
// also tried viewModel.save().join()
assertThat(viewModel.errorState.value)
.isNotNull() // doesn't fail
assertThat(viewModel.state.value.error)
.isNotNull() // fails bcz still null
// also tried
// assertThat(viewModel.state.first().error).isNotNull()
// viewModel.state.test {
// assertThat(awaitItem().error)
// .isNotNull()
// }
}
}
Albert Chang
06/27/2022, 7:06 AMBerkeli Alashov
06/27/2022, 5:55 PMrunTest
& RoboelectricBerkeli Alashov
06/27/2022, 6:28 PMstate.first()
instead of state.value
in vm.save()
to read latest set values right after calling vm.setData(true)
in tests. Do you know why that is?