In an Espresso test, how can I wait for coroutines launch()ed by a scope from rememberCoroutineScope()?

99 Views Asked by At

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()?

1

There are 1 best solutions below

0
BenjyTec On

You can use waitUntil to wait a specified maximum amount of time until a condition is true. So you just need some sort of condition to check whether the async operation was finished.

testRule.waitUntil(1000) { myCondition }

If you have asynchronous functions in your ViewModel that you want to test, the only way I found to achieve it was as follows:

1.) Move your dispatcher definition into the function signature:

fun asyncOperation(coroutineContext: CoroutineContext = Dispatchers.IO) {
    viewModelScope.launch(coroutineContext) {
        // async operation...
    }
}

2.) Call the function as runBlocking to make sure that it was executed after you reach the next statement:

@Test
fun test_asyncOperation() {

    //...
    runBlocking { myViewModel.asyncOperation(coroutineContext) }
    //... 
    // Here, you can be sure that asyncOperation completed
}