My Android app is using Jetpack Compose. Inside one of my @Composable
functions, I get a coroutine scope:
@Composable
fun MyComposable() {
val scope = rememberCoroutineScope()
// ...
Then, I use it in one of my buttons' onClick
to run a suspend function:
Button(onClick = {
scope.launch {
doThings()
}
}) {
// ...
}
}
suspend fun doThings() {
// ...
}
I'd like to do an Espresso test of the click's results. However, immediately after
onNode(/* ... */).performClick()
doThings()
isn't done yet. I'd like to wait until doThings()
finishes, without using Thread.sleep()
, something like:
waitUntil { theThingsAreDone() }
Is there any way I can do this without putting the test code in my production code?
If I could replace scope
in my test, then I could use one that incremented and decremented a CountingIdlingResource
, thus making Espresso wait for it automatically. Passing scope
as a parameter to MyComposable
might give me that control, but then I'd lose the behavior of rememberCoroutineScope()
. Can I modify scope
in my test, while having it still follow the behavior of rememberCoroutineScope()
?
The solution I went with was to use CountingIdlingResource
from expresso-idling-resource
(documentation), so that I could use composeRule.waitForIdle()
. I wrote the following helper function
val idlingResource = CountingIdlingResource("MainActivity", true)
inline fun CoroutineScope.launchIdling(crossinline block: suspend CoroutineScope.() -> Unit): Job {
idlingResource.increment()
var job: Job? = null
try {
job = launch {
try {
block()
} finally {
idlingResource.decrement()
}
}
return job
} finally {
if (job == null) idlingResource.decrement()
}
}
and a specialized version of it which was useful for the code I was writing. Then I used scope.launchIdling
instead of scope.launch
in my code, and in my tests:
@Before
fun registerIdlingResource() {
IdlingRegistry.getInstance().register(idlingResource)
}
@After
fun unregisterIdlingResource() {
IdlingRegistry.getInstance().unregister(idlingResource)
}
and waitForIdle()
worked.