Explore paused mode only for TestCoroutineDispatcher.#3
Explore paused mode only for TestCoroutineDispatcher.#3objcode wants to merge 1 commit intocoroutines-testfrom
paused mode only for TestCoroutineDispatcher.#3Conversation
|
Looks improved to me. Have you explored decoupling the test clock from the Dispatcher entirely, or at least only exposing methods that separate the tasks of advancing time and executing tasks? A test pattern I see a lot is: {
@Inject val clock: FakeClock
@Inject val testDispatcher: TestDispatcher
@Test fun testWallTimeDependentTask() {
objectUnderTest.doSomeSideEffectingAsynchronousWorkWithAWallTimeWait()
clock.advance(farEnoughMs)
testDispatcher.runCurrent() // This name confuses me a little, it implies it might only run at most one task? runAllCurrent()? runAll()?
assertThat(objectUnderTest.observableProperty).sparkles()
}
}This also ensures that the duration of time the programmer expects the task to take gets verified. If the task isn't complete after testDispatcher.awaitIdle() can print out which tasks were waiting in its thrown TimeoutException, along with a message like "these tasks were scheduled, did you forget to advance time by so that they will run: "? There are few unit tests that require wall time to elapse. When IO is faked it completes immediately, everything happens "now". When a rare test relies on wall time elapsing, I think it's important to make the programmer check that the duration is the expected one by advancing time by that amount in the unit test. I've got a thoughts on the other points, hopefully I can get time to dig in more tomorrow. |
Trying out removing the immediate mode API from TestCoroutineDispatcher and relying on runBlockingTest's event loop to pump the test lambda.
Overall, change seems like it might be heading the right direction with a large API surface reduction.
However, the automatic time advancement for the main test lambda is still present which has been fairly confusing to developers.
Things that are better
order execution in other event loops (e.g. Android Main thread) are resolved
Things that are neutral
DelayController.waitForDispatcherBusy(timeoutMillis)developers can now interact with multithreading calls directly fromTestCoroutineDispatcher. This API is not required for the runBlockingTest usage. This is important for some major parts of Android where thread checks are explict and must be satisfied. However, the API is less than obvious and it tends toward coupling tests with impl. details (possibly of transitive dependencies).Things that are worse
runCurrentto execute the body of the coroutine.withContextdepends on if the dispatcher's are equal. If so, no dispatch happens and no additional API calls are required. If they are not equal, then a call towaitForDispatcherBusy/runCurrentis required when usingTestCoroutineDispatcher. This is alleviated inrunBlockingTestsince the execution order of theadvanceTimeUntilIdlewill join thewithContextcoroutine before continuing the test.waitForDispatcherBusyafter every function that callswithContextis not a safe invocation. It will timeout and throw an exception in some invocations. This is a confusing API. How to cleanup?