I'm using `viewModelScope` to collect an endless F...
# android
l
I'm using
viewModelScope
to collect an endless Flow in my
viewModel
. In my unit test I want to "destroy" the ViewModel in order for
viewModelScope
to cancel the Flow collection. I'm calling
viewModel.onCleared()
but it doesn't cancel the `viewModelScope`'s children jobs. How can I make sure all coroutines started inside
viewModelScope
are canceled before my test ends, so
runTest
doesn't hang waiting for the coroutines to finish?
j
I would use turbine library
it has multiple APIs to cancel flows
l
Turbine is useful when I assert that the flow emitted something
Actually here the flow doesn't emit anything, it is just subscribed to
Copy code
class MyViewModel(coroutineContext: CoroutineContext = Dispatchers.Default) : ViewModel() {
    
    private val coroutineScope = viewModelScope + coroutineContext

    init {
        coroutineScope.launch { myFlow.collect { /* do something with collected items */ } }
    }
}
Copy code
class MyViewModelTest {

    @Test
    fun testInitMyViewModel() = runTest {
        MyViewModel(coroutineContext) // coroutineContext from test injected here
        // test hangs and times out eventually because myFlow collection is not canceled
    }
}
j
but turbine allows cancel it too, why you can't use it?
l
Turbine doesn't allow you to cancel collecting the flow inside of the ViewModel, only inside of your test
So MyViewModel will still hang
And
myFlow
is not even accessible inside the test, because it comes from a dependency
Turbine is meant to assert that a public (visible in tests) flow emitted something which should be asserted upon in the test
My test case is that I'm not interested in what the flow emitted and it's not part of the ViewModel's public API (the View doesn't subscribe on this flow)
So it simply cannot be tested using Turbine
u
I think you could assign launch to a job and cancel it in onCleared
l
Yes, that's one approach
However then I don't need to use
viewModelScope
I would like to avoid creating jobs for everything. That's the point of
viewModelScope
, to cancel all children jobs when the ViewModel is destroyed. The only problem is that apparently a ViewModel cannot be destroyed from a unit test.
Unit testing support is still second class citizen on Android.
i
Just creating a ViewModel doesn't tie it to any scope - you have to add it to a
ViewModelStore
via the
ViewModelProvider
APIs to actually scope it to something. It is
ViewModelStore
that has the
clear()
method that cancels and clears every ViewModel added to it
l
Yes, in my Fragments I use the
by viewModels(factoryProducer = {})
helper function to create a ViewModel tied to the Fragment's scope. My question concerned however how to do it from a unit test. Is there a possibility to also use a
ViewModelStore
inside a unit test to call
clear()
at the end of the test to cancel all children jobs of the
viewModelScope
?
i
Yep, it is just a regular class you can instantiate in your test,
ViewModelProvider
is just a regular class you can use in your test, etc.
l
Awesome, I will try that. Thank you for your help!
165 Views