How do you approach `IdlingResources` in app archi...
# android
k
How do you approach
IdlingResources
in app architecture that uses `Flow`s a lot? In my app I tried to implement similar solution to this however it doesn’t work well, because here we are waiting until job is not active anymore and it’s problematic when we, for example, observe changes in the database, because this kind of streams are not completing at all, which means we inform Espresso that app is busy all the time and tests are not resuming. Maybe it would be better to inform Espresso that the app is busy only when
Flow
is “processing” some value, however that doesn’t seem to be possible
m
You can create an IdlingResource and wrap the
launch
function to increment before launch is called, and decrement when the job completes:
Copy code
fun CoroutineScope.launchIdling(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
    EspressoIdlingResource.increment()
    val job = this.launch(context, start, block)
    job.invokeOnCompletion { EspressoIdlingResource.decrement() }
    return job
}
However, this can be pretty dangerous and error prone because if used in the wrong way, it can just lock up your espresso tests. For instance, collecting from a flow that manages UI state from your view model and updates the ui, would generally be a non terminating flow and would only exit when it’s coroutine scope went away. As such, it would never be considered idle until the composable, fragment or activity that owns it goes completely away.
I much prefer to use the waitUntil function on ComposeRule or a custom function we wrote that uses the UIController to periodically poll for some condition to be true before doign the next step
Copy code
class WaitUntilAction(
    private val timeout: Long,
    private val description: String,
    private val test: (View) -> Boolean
): ViewAction {
    override fun getConstraints(): Matcher<View> = Matchers.any(View::class.java)

    override fun getDescription(): String = description

    override fun perform(uiController: UiController, view: View) {
        val startTime = System.currentTimeMillis()
        val endTime = startTime + timeout

        do {
            if (test(view)) {
                return
            }
            uiController.loopMainThreadForAtLeast(50L)
        } while (System.currentTimeMillis() < endTime)

        throw PerformException.Builder()
            .withActionDescription(getDescription())
            .withViewDescription(HumanReadables.describe(view))
            .withCause(TimeoutException())
            .build()
    }
}
k
Thank you @mattinger the thing you described about non-terminating flow is exactly why I’m asking about it here. It’s impossible to differentiate between `launch`es for which Espresso should wait and for those that it shouldn’t in an easy way. Currently we uses similar method to yours
WaitUntilAction
, however I was exploring a way to migrate to something based on
CoroutineDispatcher
, but it seems to be not possible easy way
m
Yeah, the inability to tell the difference is an issue. You have to do that at the call site (hence the use of the launchIding function i mentioned). However, we found that using that lead to a lot of mis-use and hanging espresso tests because it was used in the wrong situations. On top of that, when you get get into compose world, you’re not even in control of the launching in a lot of cases. I tend to use use LaunchedEffect for a lot of background tasks. It’s only things in callbacks that require a coroutine where i manually launch stuff (outside a view model that is anyway)
1