Hi all :wave: I have an issue linked to an Android...
# coroutines
o
Hi all 👋 I have an issue linked to an Android integration test using
FragmentScenario
but I think it's something that could be discussed without considering such specific setup (let me know if it's not the case). In a JUnit Android instrumentation test, I launch an Android fragment using
FragmentScenario
, and my fragment impl uses
view.doOnLayout{}
(would be the same with
<http://view.post|view.post>{}
). To make my test pass, I need the code being executed in such postponed code. Here comes the link to coroutines: My test is launched on the main thread, once
fragment.onCreate
is called, I'd need to let "app logic" (here Android, but I guess it could be applicable to another use case) work and in particular get access to the main thread. My idea was to suspend the execution of the test to give back the access to main thread to whoever needs it but I can't make it work.
Copy code
@Test
    fun testMyState() {
        launchMyFragmentFragment(Lifecycle.State.RESUMED) {
            mainCoroutineRule.runBlocking {
                delay(100)
                assertEquals(3, myState.count())
            }
        }
    }
I would have expected that triggering a suspension using
delay
would give back access to main thread to my "app logic" (here
Fragment.view.doOnLayout
) but it doesn't work. Is it, by chance, a use case some know how to address?
j
I'm sorry I didn't take the time to fully read your use case, so I can't provide a solution yet. But I can tell you the source of your problem.
runBlocking
blocks the thread that calls it in order to execute what's inside. So basically the only thing that the blocked thread can run is the lambda inside
runBlocking
. Even with
delay()
the thread cannot go execute other coroutines.
o
That might not be cristal clear but in fact,
MainCoroutineRule.runBlocking
uses
runBlockingTest
under the hood. Is it the same issue? Do you think there is ways to suspend JUnit main thread execution without ending the test?
n
Try
runCurrent()
to force any “ready” coroutine to run. It’s a method on `TestCoroutineScope`/`TestCoroutineDispatcher`.
o
The code I'd like to let execute is not driven by a coroutine but by an Android
Handler
(I think), will it help in that situation too? I'll try to check
runCurrent()
I wasn't aware of, thanks.
n
I assumed based on the
MainCoroutineRule
that these were local tests, but I see in your description that they are instrumented (sorry, missed that before). My guess is that
MainCoroutineRule
sets
Dispatchers.Main
to use a
TestCoroutineDispatcher
and which is meant for local unit tests, not instrumented tests and that none of your test code is running on the main thread at all.
TestCoroutineDispatcher
uses an internal fake clock so you can test code that delays without really delaying allowing your tests to run quickly. I'd recommend just using the real main thread. I'm not sure how coroutines factors into this. If you just want to sync up with the main thread, you could try
Instrumentation.runOnMainSync
.
runBlocking(Dispatchers.Main)
would work too if end up wanting to do some coroutine stuff on the main thread.
o
Ohh, really interesting input! I'll check in that direction.
Indeed, I avoided the
resetMain
+ used
Instrumentation.waitForIdle
and it solved my issue. Thanks for this really accurate input 👍