Abhimanyu
10/02/2024, 5:04 PMStandardTestDispatcher
vs UnconfinedTestDispatcher
.
In short, StandardTestDispatcher
- does not start the coroutines eagerly. - I am stuck on how to start the coroutines when using StandardTestDispatcher
.
For UnconfinedTestDispatcher
- This starts the coroutines eagerly, but if I have multiple continuous coroutines collected in my ViewModel using collectLatest
, only the first coroutines is launched and the subsequent coroutines are not launched as the test has only one thread.
Can anyone help share how to decide the correct test dispatcher and how to resolved these issues?
I have gone through the docs and some resources, but I am still not clear on testing them. thank you colorAbhimanyu
10/02/2024, 5:05 PMZach Klippenstein (he/him) [MOD]
10/02/2024, 5:10 PMUnconfinedTestDispatcher
if at all possible. It's a huge behavior change from how stuff runs in production and will cause headaches down the road, if not right away.Abhimanyu
10/02/2024, 5:19 PMStandardTestDispatcher
.
But I am not able to understand how to launch the coroutines.Abhimanyu
10/02/2024, 5:27 PMclass MyViewModel(
coroutineScope: CoroutineScope,
) : ViewModel(
viewModelScope = coroutineScope,
) {
val stateFlow1 = MutableStateFlow(1)
val stateFlow2 = MutableStateFlow(2)
fun testMethod() {
viewModelScope.launch {
getFlow1UseCase().collectLatest {
stateFlow1.value = 10
}
}
viewModelScope.launch {
getFlow2UseCase().collectLatest {
stateFlow2.value = 20
}
}
}
}
Test
class MyViewModelTest {
private lateinit var testDispatcher: TestDispatcher
private lateinit var testScope: TestScope
private lateinit var viewModel: MyViewModel
@Before
fun setUp() = runTest {
testDispatcher = StandardTestDispatcher(
scheduler = testScheduler,
)
testScope = TestScope(
context = testDispatcher,
)
}
@Test
fun `when flows emits data, then state flows are updated accordingly`() = runTestWithTimeout {
whenever(
methodCall = getFlow1UseCase(),
).thenReturn(flowOf(10))
whenever(
methodCall = getFlow2UseCase(),
).thenReturn(flowOf(20))
initViewModel(
coroutineScope = testScope,
)
viewModel.testMethod()
assertEquals(10, viewModel.stateFlow1.value)
assertEquals(20, viewModel.stateFlow2.value)
}
private fun initViewModel(
coroutineScope: CoroutineScope,
) {
viewModel = MyViewModel(
coroutineScope = coroutineScope,
)
}
private fun runTestWithTimeout(
block: suspend TestScope.() -> Unit,
) = runTest(
timeout = 3.seconds,
testBody = block,
)
}
jw
10/02/2024, 5:32 PMviewModelScope
. Which are you actually doing?jw
10/02/2024, 5:33 PMviewModelScope
you'll need to use Dispatchers.setPain
Dispatchers.setMain
to replace the main dispatcher with the test one. Be sure to set it back in @After
.Abhimanyu
10/02/2024, 5:38 PMviewModelScope
in my ViewModel constructor.Abhimanyu
10/02/2024, 5:40 PMviewModelScope
https://developer.android.com/jetpack/androidx/releases/lifecycle#2.5.0Zach Klippenstein (he/him) [MOD]
10/02/2024, 7:33 PMrunTest
in setUp
? That doesn't make any sense since the setUp
method returns before your actual test runs. Then you're also calling runTest
for your actual test method, but then not using any of the coroutine machinery provided by the test's runTest
, instead using the stuff from the runTest
in setUp
.Zach Klippenstein (he/him) [MOD]
10/02/2024, 7:33 PMFrancesc
10/03/2024, 2:19 AMI am overriding theproviding a coroutine scope to the viewmodel constructor does not override the existingin my ViewModel constructor.viewModelScope
viewmodelScope
, you have to explicitly use the scope that you pass in the constructor to launch your coroutinesAbhimanyu
10/03/2024, 5:44 AMAnd I'm not clear what actual issue you're seeing – you said a coroutine isn't being launched? Why do you think that?I have added
println()
like this,
fun testMethod() {
println("getFlow1UseCase before launch")
viewModelScope.launch {
println("getFlow1UseCase launch")
getFlow1UseCase().collectLatest {
stateFlow1.value = 10
}
}
println("getFlow2UseCase before launch")
viewModelScope.launch {
println("getFlow2UseCase launch")
getFlow2UseCase().collectLatest {
stateFlow2.value = 20
}
}
}
I don't see getFlow2UseCase launch
being printed, hence I infer that the coroutines is not launched.
Regarding the setup,
Thanks for pointing it out, I was trying to reuse the TestDispatcher
initialization code which required runTest
.
I will do some refactoring to see if that is the root cause of my issue.Abhimanyu
10/03/2024, 5:46 AMviewModelScope
as mentioned here.
I am following this to inject the Coroutine scope in the view model and it overrides the viewModelScope
https://developer.android.com/jetpack/androidx/releases/lifecycle#2.5.0
It is mentioned here that we can provide CoroutineScope
as a parameter to ViewModel
to override the viewModelScope
.Zach Klippenstein (he/him) [MOD]
10/03/2024, 4:04 PM