kotlinx.coroutines
kotlinx.coroutines copied to clipboard
kotlinx-coroutines-test:1.6.1- Issue with collecting values of StateFlow in tests
trafficstars
There are collected only the first and the last values. It's an Android application. mainDispatcherRule is used to replace Dispatcher.Main -> UnconfinedTestDispatcher. The idea is to collect all emitted values from StateFlow. But the intermediate values are skipped. I can not grab the problem. I've referenced to this implementation
@ExperimentalCoroutinesApi
class BaseFeatureTest {
@get:Rule
val mainDispatcherRule = MainDispatcherRule()
@Test
fun `states and news are emitted correctly`() = runTest {
val sut = createBaseFeature()
val values = mutableListOf<FakeState>()
val job = launch(UnconfinedTestDispatcher(testScheduler)) {
sut.collect {
values += it
}
}
sut.emit(FakeWish.Increment)
println(values) // [FakeState(counter=100), FakeState(counter=102)]
val expectedValues = listOf(FakeState(100), FakeState(101), FakeState(102))
assertContentEquals(expectedValues, values)
job.cancel()
sut.cancel()
}
private fun createBaseFeature() = BaseFeature(
initialState,
FakeActor(),
FakeReducer(),
FakeNewsPublisher()
)
}
data class FakeState(val counter: Int)
sealed interface FakeWish {
object Increment : FakeWish
}
sealed interface FakeEffect {
object TestEffect : FakeEffect
}
sealed interface FakeNews
class FakeActor : Actor<FakeState, FakeWish, FakeEffect> {
override fun invoke(state: FakeState, wish: FakeWish): Flow<FakeEffect> {
return when(wish) {
is FakeWish.Increment -> flowOf(FakeEffect.TestEffect, FakeEffect.TestEffect)
}
}
}
class FakeReducer : Reducer<FakeState, FakeEffect> {
override fun invoke(state: FakeState, effect: FakeEffect): FakeState {
return when(effect) {
is FakeEffect.TestEffect -> {
state.copy(counter = state.counter + 1)
}
}
}
}
class FakeNewsPublisher : NewsPublisher<FakeWish, FakeEffect, FakeState, FakeNews> {
override fun invoke(wish: FakeWish, effect: FakeEffect, state: FakeState): FakeNews? {
return null
}
}
val initialState = FakeState(counter = 100)
open class BaseFeature<Wish : Any, Effect : Any, State : Any, News : Any>(
initialState: State,
private val actor: Actor<State, Wish, Effect>,
private val reducer: Reducer<State, Effect>,
private val newsPublisher: NewsPublisher<Wish, Effect, State, News>
) : Flow<State>, FlowCollector<Wish>, ViewModel() {
private val stateFlow = MutableStateFlow(initialState)
private val wishChannel = Channel<Wish>()
private val _newsChannel = Channel<News>()
val news = _newsChannel.receiveAsFlow()
private val collectJobWish: Job
init {
collectJobWish = wishChannel
.receiveAsFlow()
.onEach { wish -> processWish(stateFlow.value, wish) }
.launchIn(viewModelScope)
}
override suspend fun collect(collector: FlowCollector<State>) {
stateFlow.collect(collector)
}
override suspend fun emit(value: Wish) {
wishChannel.send(value)
}
fun cancel() {
viewModelScope.cancel()
}
private suspend fun processWish(state: State, wish: Wish) {
coroutineScope {
actor(state, wish)
.onEach { effect -> processEffect(stateFlow.value, wish, effect) }
.launchIn(this)
}
}
private suspend fun processEffect(state: State, wish: Wish, effect: Effect) {
val newState = reducer(state, effect)
stateFlow.value = newState
publishNews(wish, effect, newState)
}
private suspend fun publishNews(wish: Wish, effect: Effect, state: State) {
newsPublisher.invoke(wish, effect, state)?.let {
_newsChannel.send(it)
}
}
}