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.