https://kotlinlang.org logo
Title
a

Anastasia Finogenova

12/14/2019, 11:08 AM
Trying to test my coroutines with unit tests (JUnit Mockito) and referring kotlin coroutines repo i was using runBlockingTest and it works as expected to advance the execution . But when I run tests concurrently in a suite they are flacky as they are sharing one dispatcher (my assumption) in view model. When looking at the repo there is TestCoroutineDispatcher but it looks like the API to replace actual dispatcher with that one is only .setMain which replaces Dispatchers.Main. I am using Dispatchers.IO so the mentioned api seems of no use . Is there a way to test coroutines with this set up without injecting the dispatcher into view model? @Test fun `test loadMenu calls interactor getMenu`() = runBlockingTest { viewModel.loadMenu() Mockito.verify(interactor).getMenu() } fun loadMenu() { viewModelScope.launch(Dispatchers.IO) { try { interactor.getMenu().menuDetails.menu.run { updateMainCatList(this) } } catch (ex: Throwable) { //todo handle exceptions } } }
s

streetsofboston

12/14/2019, 12:07 PM
There is no equivalent setIO or setDefault for the IO and the Default dispatchers, like setMain.... Usually, I inject the appropriate CoroutineContext(= Dispatcher) into my ViewModels (and Interactors/UseCases/Repos/etc), allowing them to easily used in tests, injecting a TestCoroutineDispatcher, having full control over (virtual) passage of time.
Still, using Dispatcher,IO should work in your case. I think your code has uncontrolled side-effects.
b

bezrukov

12/14/2019, 6:39 PM
@streetsofboston Why do you think it should work? In example launch called on viewModelScope, and it is not affected by outer testCoroutineScope
s

streetsofboston

12/14/2019, 6:46 PM
Depends on what was meant by 'flaky'. I assumed it was race conditions due to multiple threads in Dispatchers.IO . They also could be flaky due to the fact that runBlocking ends too soon, since it only blocks until all tasks/Coroutines are finished in its TestCoroutineScope. It won't wait for all the tasks/Coroutines to finish in the viewModelScope.
b

bezrukov

12/14/2019, 7:14 PM
It's flaky not because of multiple threads in IO, but just because it's not Main - so 'viewModel.loadMenu()' may end before launch coroutine finished - so there is a rc between io and runBlockingTest's dispatcher
👍 1
a

Anastasia Finogenova

12/14/2019, 7:17 PM
That was my assumption ☝️
By flaky I mean it passes every time when I run it as a single test 30 times but running the suite 30 times give 5-6 failed times
I can see how injecting will "fix" it but imo there should be setIo the same as setMain etc , what do you think?
b

bezrukov

12/14/2019, 7:29 PM
afaik it was designed that way to not replace default dispatchers as we do that with RxJava
a

Anastasia Finogenova

12/14/2019, 7:45 PM
So it is not just me 😂 thanks I will read that, so for me to "fix" the flakiness right away the only way is to inject and then replace in the test ? That is what it seems like now ...
b

bezrukov

12/14/2019, 8:05 PM
I believe even this can not work in some cases - e.g. if your repository uses predefined dispatchers (if they differ from injected to VM) in withContext for example, result will be the same. But if your tests as simple as you described initially, that should work.
a

Anastasia Finogenova

12/14/2019, 11:16 PM
The above test is the simplest one of course, I understand what the issue is with the dispatcher, so the only way is to inject with di the same instance of dispatcher to repo and viewmodel to replace it?
b

bezrukov

12/15/2019, 9:48 PM
yep, seems so