I have random failures when writing test around AA...
# test
o
I have random failures when writing test around AAC
ViewModel
ran on JVM.
Exception while trying to handle coroutine exception
IllegalStateException: Module with the Main dispatcher had failed to initialize. For tests Dispatchers.setMain from kotlinx-coroutines-test module can be used
CoroutinesInternalError: Fatal exception in coroutines machinery for DispatchedContinuation[<http://Dispatchers.IO|Dispatchers.IO>, Continuation at com.myscript.nebo.dms.MyViewModel$1$1.invokeSuspend(CoroutinesIssueTest.kt)@52fe5814]. Please read KDoc to 'handleFatalException' method and report this incident to maintainers
(see complete stack trace in thread). I extracted a small repro unrelated to business logic but representative of my use case.
Untitled.cpp
Untitled.cpp
To easily reproduce, I use the JUnit runner (not ran with Gradle) with the repeat until failure mode, the test randomly fails (after 10, 100, 344 iterations… it depends)
If I remove the use of coroutine launched in the
init {}
there isn't any issue anymore.
If I replace the
UnconfinedTestDispatcher
by
StandardTestDispatcher
in setMain, it's the same. If I inject a work dispatcher
StandardTestDispatcher
as a replacement of
<http://Dispatchers.IO|Dispatchers.IO>
, it works.
In my real use case, it's not that simple to replace
<http://Dispatchers.IO|Dispatchers.IO>
by
StandardTestDispatcher
My tests are integration tests meant to validate asynchronous collaboration between tasks within my logic. There is a background task doing some logic in background notifying of progress. Each time a progress is notified, we check if we can continue or not. Another background task checks (in reality like every minute or so) if the condition are still met to continue background work. I'd like to test this collaboration. The test side check state of a `LiveData`and waits for some condition to be met.
e
viewModelScope
isn't well testable, https://github.com/Kotlin/kotlinx.coroutines/issues/3298 and other issues
#android and #coroutines would probably have more responses, but in general
viewModelScope
is not the lifecycle you want. either use
viewLifecycleOwner.lifecycleScope
from outside the ViewModel, or something scoped to the application lifecycle
o
I'm not sure to follow, you suggest to inject a different scope for test? In "production" as well
viewModelScope
isn't considered the good choice?
e
you should be able to use the same VM code in both test and production. if the operation is UI-scoped, a simple
suspend fun
that the UI can launch from its own lifecycle scope is fine, and you can use the standard
runTest
function. if it's really supposed to be some long-running background operation, then you should maybe reconsider the approach (e.g. WorkManager implementation) but regardless, inject the thing handling background work
o
Sorry, I forgot the share feedback on that đŸ˜­. I injected a
customCoroutineScope
(
CoroutineScope(UnconfinedTestDispatcher())
) in my
ViewModel
ctor (if none provided, falls back to AndroidX
viewModelScope
) and situation highly improved indeed đŸ¤¯. I didn't expect that the issue came from
viewModelScope
in test context. Thanks @ephemient for this enlightening explanation/hint đŸ™‡. (was too invasive to change the design of the public API under test like suggested)
e
if you're using
runTest
, there's backgroundScope which you can use instead of
CoroutineScope()
with the benefit that failures inside that scope get reported
o
right away as is, in my current setup, it doesn't work, I'll try to dedicate some time for that to better identify why anyway, I can already move forward with the first shot
(the way my background work is managed might not be compatible with
backgroundScope
right now, now need to understand why)
c
AFAK, on the test, you should use one thread(Main).