Skip to content

Provide a way to determine if TestCoroutineDispatcher is "idle" #1202

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
objcode opened this issue May 17, 2019 · 6 comments
Open

Provide a way to determine if TestCoroutineDispatcher is "idle" #1202

objcode opened this issue May 17, 2019 · 6 comments
Labels

Comments

@objcode
Copy link
Contributor

objcode commented May 17, 2019

When writing an advanced test that uses multiple related TestCoroutineDispatcher instances, there is a problem that can occur when one dispatches a task to another:

val dispatcherA = TestCoroutineDispatcher()
val dispatcherB = TestCoroutineDispatcher()

// inject those somehow to the class under test

@Test
fun test_withMultipleDispatchers() {
    dispatcherA.pause()
    dispatcherB.pause()
    
    foo()
    
    dispatcherA.runUntilIdle()
    dispatcherB.runUntilIdle()
    // executing tasks on B enqueued another task on A. Currently there is no way to determine if both of them are actually idle.
}

fun foo() {
    scope.launch(dispatcherA) {
        bar()
    }
}

fun bar() {
    scope.launch(dispatcherB) {
        foo()
    }
}

In this contrived example, there's an infinite recursion so it will never resolve, but this problem comes up in normal test code.

This also comes up in a more typical case of testing withContext. To test withContext is called, the easiest way is to inject a dispatcher to intercept the coroutine started by withContext. However, in the current API for TestCoroutineDispatcher there is no way to determine if a task has been sent to a dispatcher.

In addition, this sort of inspection is important for implementing an Espresso IdlingResource as discussed in #242.

A bit of a complication here (since this should fit well with the Espresso integration which likely needs to work w/ regular dispatchers). A dispatcher has the ability to differentiate between two states without introducing extra tracking:

  1. Idle (all coroutines are suspended, or delayed into the future)
  2. Busy (some coroutine is currently running)

However, a TestCoroutineDispatcher can differentiate between three states

  1. Idle (all coroutines are suspended)
  2. Busy (some coroutine is currently running)
  3. DelayQueued(long) (some coroutine will resume after long delay)

No concrete API proposals at this second - wanted to make a issue to track this.

@elizarov
Copy link
Contributor

But why mutliple test dispatchers are needed? Why one is not enough? What practical problem is being solved by using multiple test dispatchers?

@objcode
Copy link
Contributor Author

objcode commented May 29, 2019

cc @yigit @manuelvicnt who have provided the feedback that they wanted use multiple dispatchers.

@manuelvicnt
Copy link

why mutliple test dispatchers are needed?

When testing complex scenarios that involve multiple dispatchers (for example in Android, a ViewModel using main and computation), you might want to test the permutations (suspended, running, etc) of different coroutines running at the same time.

Obviously, you wouldn't inject multiple TestCoroutineDispatcher in all the tests, just in those covering these edge cases.

@streetsofboston
Copy link

I'm curious: Is this for testing possible race-conditions that could happen between UI coroutines running on the main dispatcher vs storage/networking coroutines running on the IO dispatcher in the actual app?

@TWiStErRob
Copy link

But why mutliple test dispatchers are needed? Why one is not enough? What practical problem is being solved by using multiple test dispatchers?

@elizarov on Android Espresso UI tests, we are testing the application from within. The Android apps are running on different dispatchers to make sure the background work is not blocking the user from clicking. We want to test these apps from Espresso JUnit tests running on devices which have the concept of IdlingResources, which block every single assertion/action within Espresso: all registered idling resources have to be idle before anything is actioned/verified. This is to wait for background work to mutate the UI, similar to how a manual tester / user would wait for the app to settle (e.g. progress bars to disappear, results to appear) before continuing on. To implement this we could use a TestCoroutineDispatcher to listen for idle and an IdlingResource to block Espresso when not idle. We would wrap each of the IO/Main/Computation dispatchers individually. When running tests we want to get an environment as close to real as possible, so using a single dispatcher would mean we would be change real behavior and hiding potential threading issues, while all we want is to know "when things are done".

@TheKeeperOfPie
Copy link

Has there been any progress on this? I noticed the API already exists on TestCoroutineScheduler here, but it's marked internal. Any way that could just be marked public? That would solve my use case, where I want to validate that a call didn't schedule any work on a dispatcher.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

7 participants