Update TestCoroutineContext to support structured concurrency. #3
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Updates
TestCoroutineContext
to support structured concurrency.Issue: Kotlin#541
After Kotlin#810 lands this should move into
kotlinx-coroutines-test
. For now, putting up for API/code review.Changes:
TestCoroutineContext
intoTestCoroutineDispatcher
,TestCoroutineExceptionHandler
andTestCoroutineScope
DelayController
interface for testing libraries to expose Dispatcher controlExceptionCaptor
inerface for testing libraries to expose uncaught exceptionsrunBlockingTest
andasyncTest
withTestContext
Usage (main entry points)
runBlockingTest
runBlockingTest
is an immediate executor, delays will always auto-progress from eitherdispatch
orscheduleResumeAfterDelay
. Note that it provides a suspend lambda.fun runBlockingTest(context: CoroutineContext? = null, testBody: suspend CoroutineScope.() -> Unit)
asyncTest
asyncTest
is a lazy executor, any dispatch or scheduled events will be stored in a queue that must be manually progressed. Note, it does not provide a suspend lambda (see discussion below).It is intended to be an advanced API that offers full control over dispatcher execution. In addition to offering detailed control of execution, it also provides extra test safety by ensuring all uncaught exceptions are handled and all launched coroutines are not active at the end of the test.
fun asyncTest(context: CoroutineContext? = null, testBody: TestCoroutineScope.() -> Unit) {
TestCoroutineDispatcher / TestCoroutineScope
When used in conjunction with Kotlin#810 most tests will declare a dispatcher as part of the setup method. As a convenience extension functions are provided
needs review: Tests that only have a dispatcher are allowed access to
runBlockingTest
as well without constructing a scoperunBlocking with time control
Tests can use runBlocking with time control if they pass the dispatcher directly:
Library friendly interfaces
To help testing libraries expose test control, the time control and exception handling are split into interfaces. This allows a testing library to expose a smaller interface to tests than
TestCoroutineDispatcher
as well as combine the two interfaces onto a control object. In the current impl.,TestCoroutineScope
delegates both interfaces.Ideally, testing libraries would prefer to expose these smaller interfaces. For example a JUnit4 rule might expose
DelayController
and delegate it to an instance ofTestCoroutineDispatcher
.(discussion) suspend lambda on builders
Initially, it would provide a suspend lambda to
asyncTest
, however it turned out to be impossible to reconcile a dispatcher that never executed immediately and a test written like this:It is expected this test is not correct, since it does not call
runCurrent
or another function that would execute the pending tasks. However, since it is driven by a non-suspend function that is manually manipulating the dispatcher, it is not possible to both auto-advance through main body while not auto-advancing into the launch body.Auto-advancing the main body would require that the dispatcher run any immediately scheduled task in the order they were scheduled, which will break the contract of
runCurrent
.