Does anyone has guidance on the best way to test S...
# coroutines
c
Does anyone has guidance on the best way to test StateFlows that uses
stateIn
operator? Given the fake code below when running tests using Turbine they will never emit anything. They do emit if I remove
stateIn
and move everything to a function.
Copy code
Service = suspend fun fetchData(): String = "Hello World"
FlowService = fun fetchData(): Flow<String> = flow {
    emit("hi there") /* some cold emission */
}

class ViewModel(
        coroutineScope: CoroutineScope,
        service: Service,
        flowService: FlowService
) {
  val state1: StateFlow<String> = flow { emit(service.fetchData()) }.stateIn(coroutineScope, SharingStarted.WhileSubscribed(5000), "Loading")
  val state2: StateFlow<String> = flowService.fetchData().stateIn(coroutineScope, SharingStarted.WhileSubscribed(5000), "Loading")
}
👀 1
Every case in here will fail because flow never emits:
Copy code
@Test fun expectStates() = scope.runTest {
        turbineScope {
            val viewModel = ViewModel(scope, Service(), FlowService())

            // fails
            viewModel.state1.test {
                assertEquals("Loading", awaitItem())
                assertEquals("Hello World", awaitItem())
                cancelAndIgnoreRemainingEvents()
            }

            // fails
            viewModel.state2.test {
                assertEquals("Loading", awaitItem())
                assertEquals("Hello World", awaitItem())
                cancelAndIgnoreRemainingEvents()
            }

            // fails
            backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) {
                viewModel.state2.collect()
            }

            viewModel.state2.test {
                assertEquals("Loading", awaitItem())
                assertEquals("Hello World", awaitItem())
                cancelAndIgnoreRemainingEvents()
            }
        }
    }
j
You don't need
turbineScope
here because you're not using any standalone `Turbine`s
I would expect the first
test { }
block to fail because it will never see
Loading
Even if it did, I would expect the next two
test
blocks to fail because no time has elapsed to restart the upstream collection and re-trigger emission of
Loading
You don't need any of those
cancelAndIgnoreRemainingEvents()
calls
You also really don't need
Turbine
at all when testing a
StateFlow
. You can do normal assertions against its
.value
What is your goal with this test? What behavior are you trying to assert exists within the view model?
c
Well, I guess that's why there are no documentation regarding state flow on turbine. That's fine. Thanks for the explanation! I expect my VM to produce states whenever my SQLDelight flow stream emits a new value, so I tried using turbine here for this case but as you said I may not really need it.
Do we always need the empty collector as Google state in their documentation regarding testing StateFlow?
Also the reason I tried to use Turbine was to have a similar behavior we have when using TestObserver from RxJava
j
With the empty collector you keep the underlying flow active. Since it's collecting in the
backgroundScope
it will be automatically canceled when the test ends.
👍 1
c
Ok. Great. It works normally with your suggestions. Thanks!