question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

EspressoIdlingResource integration

See original GitHub issue

Would be nice to have a wrapper to easily provide support for IdlingResource.

Some examples

EDIT: I’m currently injecting my CoroutineDispatchers like so:

open class AppDispatchers(
    val ui: CoroutineDispatcher = UI,
    val background: CoroutineDispatcher = backgroundDefault,
    val network: CoroutineDispatcher = networkDefault
)

For espresso testing, which monitors the async task pool and UI thread for idle conditions, I’m injecting the following:

class EspressoDispatchers : AppDispatchers(
    ui = UI,
    background = AsyncTask.THREAD_POOL_EXECUTOR.asCoroutineDispatcher(),
    network = AsyncTask.THREAD_POOL_EXECUTOR.asCoroutineDispatcher()
)

Here’s the problem I’m experiencing, which happens about 1% of the time on our automated tests.

  1. Network call completes.
  2. a repository level ConflatedBroadcastChannel is updated with the information from the network call.
    • done by the background dispatcher
  3. Espresso thinks app is now idle, and throws exception because the app isn’t idle yet (see number 4)
  4. a ConflatedBroadcastChannel in the ViewModel (which has been observing the repository level ConflatedBroadcastChannel the whole time) is updated
    • done by the background dispatcher
    • this happens after Espresso thinks the app is idle (espresso is wrong)

Issue Analytics

  • State:closed
  • Created 6 years ago
  • Reactions:51
  • Comments:35 (15 by maintainers)

github_iconTop GitHub Comments

15reactions
LUwaisAcommented, Dec 4, 2019

We have been using a delegate pattern as mentioned in objcode’s comment. It is a similar idea to the code written by @yigit but allows tests to behave more similarly to production code by wrapping production dispatchers.

class EspressoTrackedDispatcher(private val wrappedCoroutineDispatcher: CoroutineDispatcher) : CoroutineDispatcher() {
    private val counter: CountingIdlingResource = CountingIdlingResource("EspressoTrackedDispatcher for $wrappedCoroutineDispatcher")
    init {
        IdlingRegistry.getInstance().register(counter)
    }

    override fun dispatch(context: CoroutineContext, block: Runnable) {
        counter.increment()
        val blockWithDecrement = Runnable {
            try {
                block.run()
            } finally {
                counter.decrement()
            }
        }
        wrappedCoroutineDispatcher.dispatch(context, blockWithDecrement)
    }

    fun cleanUp() {
        IdlingRegistry.getInstance().unregister(counter)
    }
}

I’m then using the above Dispatcher in a TestRule:

class DispatcherIdlerRule: TestRule {
    override fun apply(base: Statement?, description: Description?): Statement =
        object : Statement() {
            override fun evaluate() {
                val espressoTrackedDispatcherIO = EspressoTrackedDispatcher(Dispatchers.IO)
                val espressoTrackedDispatcherDefault = EspressoTrackedDispatcher(Dispatchers.Default)
                MyDispatchers.IO = espressoTrackedDispatcherIO
                MyDispatchers.Default = espressoTrackedDispatcherDefault
                try {
                    base?.evaluate()
                } finally {
                    espressoTrackedDispatcherIO.cleanUp()
                    espressoTrackedDispatcherDefault.cleanUp()
                    MyDispatchers.resetAll()
                }
            }
        }
}

In the absence of setIO()/setDefault() methods the MyDispatchers class is something we’ve added to prod code to allow the extra flexibility of setting the dispatchers. It’s fairly simple and mimics (and adds to) the public API of Dispatchers. I would rather not have this class but thought it is a small addition and is easily replaced when better options become available:

object MyDispatchers {
    var Main: CoroutineDispatcher = Dispatchers.Main
    var IO: CoroutineDispatcher = Dispatchers.IO
    var Default: CoroutineDispatcher = Dispatchers.Default

    fun resetMain() {
        Main = Dispatchers.Main
    }

    fun resetIO() {
        IO = Dispatchers.IO
    }

    fun resetDefault() {
        Default = Dispatchers.Default
    }

    fun resetAll() {
        resetMain()
        resetIO()
        resetDefault()
    }
}

With this approach, we’ve unblocked our Espresso testing. Caveat: For our use case we don’t currently need it to behave more effectively but the above code tells Espresso that the app is idle during a delay() in production code.

Hope this helps for anyone else struggling with this issue

9reactions
TWiStErRobcommented, Oct 28, 2022

Please tell me that “Close” was a mistake… 🫣 4 years old issue, top 4 voted, that affects pretty much every tested Android project. With this scope, a comment accompanying the close would be nice 🙏.


What would be necessary to get first-party support? Would Google (android-test) consider taking this? Are all the APIs existing and open to implement this correctly without hacking?

Read more comments on GitHub >

github_iconTop Results From Across the Web

Espresso idling resources - Android Developers
... Example idling resource implementations; Create your own idling resource; Register idling resources; Integrate idling resources into your app.
Read more >
Integrate Espresso Idling Resources in your app to build ...
We can now enjoy and sit back while EspressoIdlingResource handles all the synchronization that was required. There is no need to use those...
Read more >
How to synchronize Espresso UI tests - Coding Troops
... we ought to integrate an implementation of the IdlingResources. ... 'com.android.support.test.espresso:espresso-idling-resource:3.0.2' ...
Read more >
Espresso Idling Resource (UI Testing for Beginners PART 14)
This video is part of a FREE course: https://codingwithmitch.com/courses/ui-testing-for-beginners/In this video I show you how to use ...
Read more >
Blog: Espresso Idling Resources - Tudip Technologies
getInstance().unregister(EspressoIdlingResource. ... Related Blogs. How to Accept Payment on your site using Open Edge Payment Integration?
Read more >

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found