You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
458 lines
20 KiB
458 lines
20 KiB
4 months ago
|
# Module kotlinx-coroutines-test
|
||
|
|
||
|
Test utilities for `kotlinx.coroutines`.
|
||
|
|
||
|
This package provides testing utilities for effectively testing coroutines.
|
||
|
|
||
|
## Using in your project
|
||
|
|
||
|
Add `kotlinx-coroutines-test` to your project test dependencies:
|
||
|
```
|
||
|
dependencies {
|
||
|
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.4.1'
|
||
|
}
|
||
|
```
|
||
|
|
||
|
**Do not** depend on this project in your main sources, all utilities are intended and designed to be used only from tests.
|
||
|
|
||
|
## Dispatchers.Main Delegation
|
||
|
|
||
|
`Dispatchers.setMain` will override the `Main` dispatcher in test situations. This is helpful when you want to execute a
|
||
|
test in situations where the platform `Main` dispatcher is not available, or you wish to replace `Dispatchers.Main` with a
|
||
|
testing dispatcher.
|
||
|
|
||
|
Once you have this dependency in the runtime,
|
||
|
[`ServiceLoader`](https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html) mechanism will overwrite
|
||
|
[Dispatchers.Main] with a testable implementation.
|
||
|
|
||
|
You can override the `Main` implementation using [setMain][setMain] method with any [CoroutineDispatcher] implementation, e.g.:
|
||
|
|
||
|
```kotlin
|
||
|
|
||
|
class SomeTest {
|
||
|
|
||
|
private val mainThreadSurrogate = newSingleThreadContext("UI thread")
|
||
|
|
||
|
@Before
|
||
|
fun setUp() {
|
||
|
Dispatchers.setMain(mainThreadSurrogate)
|
||
|
}
|
||
|
|
||
|
@After
|
||
|
fun tearDown() {
|
||
|
Dispatchers.resetMain() // reset main dispatcher to the original Main dispatcher
|
||
|
mainThreadSurrogate.close()
|
||
|
}
|
||
|
|
||
|
@Test
|
||
|
fun testSomeUI() = runBlocking {
|
||
|
launch(Dispatchers.Main) { // Will be launched in the mainThreadSurrogate dispatcher
|
||
|
// ...
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
Calling `setMain` or `resetMain` immediately changes the `Main` dispatcher globally. The testable version of
|
||
|
`Dispatchers.Main` installed by the `ServiceLoader` will delegate to the dispatcher provided by `setMain`.
|
||
|
|
||
|
## runBlockingTest
|
||
|
|
||
|
To test regular suspend functions or coroutines started with `launch` or `async` use the [runBlockingTest] coroutine
|
||
|
builder that provides extra test control to coroutines.
|
||
|
|
||
|
1. Auto-advancing of time for regular suspend functions
|
||
|
2. Explicit time control for testing multiple coroutines
|
||
|
3. Eager execution of `launch` or `async` code blocks
|
||
|
4. Pause, manually advance, and restart the execution of coroutines in a test
|
||
|
5. Report uncaught exceptions as test failures
|
||
|
|
||
|
### Testing regular suspend functions
|
||
|
|
||
|
To test regular suspend functions, which may have a delay, you can use the [runBlockingTest] builder to start a testing
|
||
|
coroutine. Any calls to `delay` will automatically advance virtual time by the amount delayed.
|
||
|
|
||
|
```kotlin
|
||
|
@Test
|
||
|
fun testFoo() = runBlockingTest { // a coroutine with an extra test control
|
||
|
val actual = foo()
|
||
|
// ...
|
||
|
}
|
||
|
|
||
|
suspend fun foo() {
|
||
|
delay(1_000) // auto-advances virtual time by 1_000ms due to runBlockingTest
|
||
|
// ...
|
||
|
}
|
||
|
```
|
||
|
|
||
|
`runBlockingTest` returns `Unit` so it may be used in a single expression with common testing libraries.
|
||
|
|
||
|
### Testing `launch` or `async`
|
||
|
|
||
|
Inside of [runBlockingTest], both [launch] and [async] will start a new coroutine that may run concurrently with the
|
||
|
test case.
|
||
|
|
||
|
To make common testing situations easier, by default the body of the coroutine is executed *eagerly* until
|
||
|
the first call to [delay] or [yield].
|
||
|
|
||
|
```kotlin
|
||
|
@Test
|
||
|
fun testFooWithLaunch() = runBlockingTest {
|
||
|
foo()
|
||
|
// the coroutine launched by foo() is completed before foo() returns
|
||
|
// ...
|
||
|
}
|
||
|
|
||
|
fun CoroutineScope.foo() {
|
||
|
// This coroutines `Job` is not shared with the test code
|
||
|
launch {
|
||
|
bar() // executes eagerly when foo() is called due to runBlockingTest
|
||
|
println(1) // executes eagerly when foo() is called due to runBlockingTest
|
||
|
}
|
||
|
}
|
||
|
|
||
|
suspend fun bar() {}
|
||
|
```
|
||
|
|
||
|
`runBlockingTest` will auto-progress virtual time until all coroutines are completed before returning. If any coroutines
|
||
|
are not able to complete, an [UncompletedCoroutinesError] will be thrown.
|
||
|
|
||
|
*Note:* The default eager behavior of [runBlockingTest] will ignore [CoroutineStart] parameters.
|
||
|
|
||
|
### Testing `launch` or `async` with `delay`
|
||
|
|
||
|
If the coroutine created by `launch` or `async` calls `delay` then the [runBlockingTest] will not auto-progress time
|
||
|
right away. This allows tests to observe the interaction of multiple coroutines with different delays.
|
||
|
|
||
|
To control time in the test you can use the [DelayController] interface. The block passed to
|
||
|
[runBlockingTest] can call any method on the `DelayController` interface.
|
||
|
|
||
|
```kotlin
|
||
|
@Test
|
||
|
fun testFooWithLaunchAndDelay() = runBlockingTest {
|
||
|
foo()
|
||
|
// the coroutine launched by foo has not completed here, it is suspended waiting for delay(1_000)
|
||
|
advanceTimeBy(1_000) // progress time, this will cause the delay to resume
|
||
|
// the coroutine launched by foo has completed here
|
||
|
// ...
|
||
|
}
|
||
|
|
||
|
suspend fun CoroutineScope.foo() {
|
||
|
launch {
|
||
|
println(1) // executes eagerly when foo() is called due to runBlockingTest
|
||
|
delay(1_000) // suspends until time is advanced by at least 1_000
|
||
|
println(2) // executes after advanceTimeBy(1_000)
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
*Note:* `runBlockingTest` will always attempt to auto-progress time until all coroutines are completed just before
|
||
|
exiting. This is a convenience to avoid having to call [advanceUntilIdle][DelayController.advanceUntilIdle]
|
||
|
as the last line of many common test cases.
|
||
|
If any coroutines cannot complete by advancing time, an [UncompletedCoroutinesError] is thrown.
|
||
|
|
||
|
### Testing `withTimeout` using `runBlockingTest`
|
||
|
|
||
|
Time control can be used to test timeout code. To do so, ensure that the function under test is suspended inside a
|
||
|
`withTimeout` block and advance time until the timeout is triggered.
|
||
|
|
||
|
Depending on the code, causing the code to suspend may need to use different mocking or fake techniques. For this
|
||
|
example an uncompleted `Deferred<Foo>` is provided to the function under test via parameter injection.
|
||
|
|
||
|
```kotlin
|
||
|
@Test(expected = TimeoutCancellationException::class)
|
||
|
fun testFooWithTimeout() = runBlockingTest {
|
||
|
val uncompleted = CompletableDeferred<Foo>() // this Deferred<Foo> will never complete
|
||
|
foo(uncompleted)
|
||
|
advanceTimeBy(1_000) // advance time, which will cause the timeout to throw an exception
|
||
|
// ...
|
||
|
}
|
||
|
|
||
|
fun CoroutineScope.foo(resultDeferred: Deferred<Foo>) {
|
||
|
launch {
|
||
|
withTimeout(1_000) {
|
||
|
resultDeferred.await() // await() will suspend forever waiting for uncompleted
|
||
|
// ...
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
*Note:* Testing timeouts is simpler with a second coroutine that can be suspended (as in this example). If the
|
||
|
call to `withTimeout` is in a regular suspend function, consider calling `launch` or `async` inside your test body to
|
||
|
create a second coroutine.
|
||
|
|
||
|
### Using `pauseDispatcher` for explicit execution of `runBlockingTest`
|
||
|
|
||
|
The eager execution of `launch` and `async` bodies makes many tests easier, but some tests need more fine grained
|
||
|
control of coroutine execution.
|
||
|
|
||
|
To disable eager execution, you can call [pauseDispatcher][DelayController.pauseDispatcher]
|
||
|
to pause the [TestCoroutineDispatcher] that [runBlockingTest] uses.
|
||
|
|
||
|
When the dispatcher is paused, all coroutines will be added to a queue instead running. In addition, time will never
|
||
|
auto-progress due to `delay` on a paused dispatcher.
|
||
|
|
||
|
```kotlin
|
||
|
@Test
|
||
|
fun testFooWithPauseDispatcher() = runBlockingTest {
|
||
|
pauseDispatcher {
|
||
|
foo()
|
||
|
// the coroutine started by foo has not run yet
|
||
|
runCurrent() // the coroutine started by foo advances to delay(1_000)
|
||
|
// the coroutine started by foo has called println(1), and is suspended on delay(1_000)
|
||
|
advanceTimeBy(1_000) // progress time, this will cause the delay to resume
|
||
|
// the coroutine started by foo has called println(2) and has completed here
|
||
|
}
|
||
|
// ...
|
||
|
}
|
||
|
|
||
|
fun CoroutineScope.foo() {
|
||
|
launch {
|
||
|
println(1) // executes after runCurrent() is called
|
||
|
delay(1_000) // suspends until time is advanced by at least 1_000
|
||
|
println(2) // executes after advanceTimeBy(1_000)
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Using `pauseDispatcher` gives tests explicit control over the progress of time as well as the ability to enqueue all
|
||
|
coroutines. As a best practice consider adding two tests, one paused and one eager, to test coroutines that have
|
||
|
non-trivial external dependencies and side effects in their launch body.
|
||
|
|
||
|
*Important:* When passed a lambda block, `pauseDispatcher` will resume eager execution immediately after the block.
|
||
|
This will cause time to auto-progress if there are any outstanding `delay` calls that were not resolved before the
|
||
|
`pauseDispatcher` block returned. In advanced situations tests can call [pauseDispatcher][DelayController.pauseDispatcher]
|
||
|
without a lambda block and then explicitly resume the dispatcher with [resumeDispatcher][DelayController.resumeDispatcher].
|
||
|
|
||
|
## Integrating tests with structured concurrency
|
||
|
|
||
|
Code that uses structured concurrency needs a [CoroutineScope] in order to launch a coroutine. In order to integrate
|
||
|
[runBlockingTest] with code that uses common structured concurrency patterns tests can provide one (or both) of these
|
||
|
classes to application code.
|
||
|
|
||
|
| Name | Description |
|
||
|
| ---- | ----------- |
|
||
|
| [TestCoroutineScope] | A [CoroutineScope] which provides detailed control over the execution of coroutines for tests and integrates with [runBlockingTest]. |
|
||
|
| [TestCoroutineDispatcher] | A [CoroutineDispatcher] which can be used for tests and integrates with [runBlockingTest]. |
|
||
|
|
||
|
Both classes are provided to allow for various testing needs. Depending on the code that's being
|
||
|
tested, it may be easier to provide a [TestCoroutineDispatcher]. For example [Dispatchers.setMain][setMain] will accept
|
||
|
a [TestCoroutineDispatcher] but not a [TestCoroutineScope].
|
||
|
|
||
|
[TestCoroutineScope] will always use a [TestCoroutineDispatcher] to execute coroutines. It
|
||
|
also uses [TestCoroutineExceptionHandler] to convert uncaught exceptions into test failures.
|
||
|
|
||
|
By providing [TestCoroutineScope] a test case is able to control execution of coroutines, as well as ensure that
|
||
|
uncaught exceptions thrown by coroutines are converted into test failures.
|
||
|
|
||
|
### Providing `TestCoroutineScope` from `runBlockingTest`
|
||
|
|
||
|
In simple cases, tests can use the [TestCoroutineScope] created by [runBlockingTest] directly.
|
||
|
|
||
|
```kotlin
|
||
|
@Test
|
||
|
fun testFoo() = runBlockingTest {
|
||
|
foo() // runBlockingTest passed in a TestCoroutineScope as this
|
||
|
}
|
||
|
|
||
|
fun CoroutineScope.foo() {
|
||
|
launch { // CoroutineScope for launch is the TestCoroutineScope provided by runBlockingTest
|
||
|
// ...
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
This style is preferred when the `CoroutineScope` is passed through an extension function style.
|
||
|
|
||
|
### Providing an explicit `TestCoroutineScope`
|
||
|
|
||
|
In many cases, the direct style is not preferred because [CoroutineScope] may need to be provided through another means
|
||
|
such as dependency injection or service locators.
|
||
|
|
||
|
Tests can declare a [TestCoroutineScope] explicitly in the class to support these use cases.
|
||
|
|
||
|
Since [TestCoroutineScope] is stateful in order to keep track of executing coroutines and uncaught exceptions, it is
|
||
|
important to ensure that [cleanupTestCoroutines][TestCoroutineScope.cleanupTestCoroutines] is called after every test case.
|
||
|
|
||
|
```kotlin
|
||
|
class TestClass {
|
||
|
private val testScope = TestCoroutineScope()
|
||
|
private lateinit var subject: Subject
|
||
|
|
||
|
@Before
|
||
|
fun setup() {
|
||
|
// provide the scope explicitly, in this example using a constructor parameter
|
||
|
subject = Subject(testScope)
|
||
|
}
|
||
|
|
||
|
@After
|
||
|
fun cleanUp() {
|
||
|
testScope.cleanupTestCoroutines()
|
||
|
}
|
||
|
|
||
|
@Test
|
||
|
fun testFoo() = testScope.runBlockingTest {
|
||
|
// TestCoroutineScope.runBlockingTest uses the Dispatcher and exception handler provided by `testScope`
|
||
|
subject.foo()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class Subject(val scope: CoroutineScope) {
|
||
|
fun foo() {
|
||
|
scope.launch {
|
||
|
// launch uses the testScope injected in setup
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
*Note:* [TestCoroutineScope], [TestCoroutineDispatcher], and [TestCoroutineExceptionHandler] are interfaces to enable
|
||
|
test libraries to provide library specific integrations. For example, a JUnit4 `@Rule` may call
|
||
|
[Dispatchers.setMain][setMain] then expose [TestCoroutineScope] for use in tests.
|
||
|
|
||
|
### Providing an explicit `TestCoroutineDispatcher`
|
||
|
|
||
|
While providing a [TestCoroutineScope] is slightly preferred due to the improved uncaught exception handling, there are
|
||
|
many situations where it is easier to provide a [TestCoroutineDispatcher]. For example [Dispatchers.setMain][setMain]
|
||
|
does not accept a [TestCoroutineScope] and requires a [TestCoroutineDispatcher] to control coroutine execution in
|
||
|
tests.
|
||
|
|
||
|
The main difference between `TestCoroutineScope` and `TestCoroutineDispatcher` is how uncaught exceptions are handled.
|
||
|
When using `TestCoroutineDispatcher` uncaught exceptions thrown in coroutines will use regular
|
||
|
[coroutine exception handling](https://kotlinlang.org/docs/reference/coroutines/exception-handling.html).
|
||
|
`TestCoroutineScope` will always use `TestCoroutineDispatcher` as it's dispatcher.
|
||
|
|
||
|
A test can use a `TestCoroutineDispatcher` without declaring an explicit `TestCoroutineScope`. This is preferred
|
||
|
when the class under test allows a test to provide a [CoroutineDispatcher] but does not allow the test to provide a
|
||
|
[CoroutineScope].
|
||
|
|
||
|
Since [TestCoroutineDispatcher] is stateful in order to keep track of executing coroutines, it is
|
||
|
important to ensure that [cleanupTestCoroutines][DelayController.cleanupTestCoroutines] is called after every test case.
|
||
|
|
||
|
```kotlin
|
||
|
class TestClass {
|
||
|
private val testDispatcher = TestCoroutineDispatcher()
|
||
|
|
||
|
@Before
|
||
|
fun setup() {
|
||
|
// provide the scope explicitly, in this example using a constructor parameter
|
||
|
Dispatchers.setMain(testDispatcher)
|
||
|
}
|
||
|
|
||
|
@After
|
||
|
fun cleanUp() {
|
||
|
Dispatchers.resetMain()
|
||
|
testDispatcher.cleanupTestCoroutines()
|
||
|
}
|
||
|
|
||
|
@Test
|
||
|
fun testFoo() = testDispatcher.runBlockingTest {
|
||
|
// TestCoroutineDispatcher.runBlockingTest uses `testDispatcher` to run coroutines
|
||
|
foo()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fun foo() {
|
||
|
MainScope().launch {
|
||
|
// launch will use the testDispatcher provided by setMain
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
*Note:* Prefer to provide `TestCoroutineScope` when it does not complicate code since it will also elevate exceptions
|
||
|
to test failures. However, exposing a `CoroutineScope` to callers of a function may lead to complicated code, in which
|
||
|
case this is the preferred pattern.
|
||
|
|
||
|
### Using `TestCoroutineScope` and `TestCoroutineDispatcher` without `runBlockingTest`
|
||
|
|
||
|
It is supported to use both [TestCoroutineScope] and [TestCoroutineDispatcher] without using the [runBlockingTest]
|
||
|
builder. Tests may need to do this in situations such as introducing multiple dispatchers and library writers may do
|
||
|
this to provide alternatives to `runBlockingTest`.
|
||
|
|
||
|
```kotlin
|
||
|
@Test
|
||
|
fun testFooWithAutoProgress() {
|
||
|
val scope = TestCoroutineScope()
|
||
|
scope.foo()
|
||
|
// foo is suspended waiting for time to progress
|
||
|
scope.advanceUntilIdle()
|
||
|
// foo's coroutine will be completed before here
|
||
|
}
|
||
|
|
||
|
fun CoroutineScope.foo() {
|
||
|
launch {
|
||
|
println(1) // executes eagerly when foo() is called due to TestCoroutineScope
|
||
|
delay(1_000) // suspends until time is advanced by at least 1_000
|
||
|
println(2) // executes after advanceTimeUntilIdle
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
## Using time control with `withContext`
|
||
|
|
||
|
Calls to `withContext(Dispatchers.IO)` or `withContext(Dispatchers.Default)` are common in coroutines based codebases.
|
||
|
Both dispatchers are not designed to interact with `TestCoroutineDispatcher`.
|
||
|
|
||
|
Tests should provide a `TestCoroutineDispatcher` to replace these dispatchers if the `withContext` calls `delay` in the
|
||
|
function under test. For example, a test that calls `veryExpensiveOne` should provide a `TestCoroutineDispatcher` using
|
||
|
either dependency injection, a service locator, or a default parameter.
|
||
|
|
||
|
```kotlin
|
||
|
suspend fun veryExpensiveOne() = withContext(Dispatchers.Default) {
|
||
|
delay(1_000)
|
||
|
1 // for very expensive values of 1
|
||
|
}
|
||
|
```
|
||
|
|
||
|
In situations where the code inside the `withContext` is very simple, it is not as important to provide a test
|
||
|
dispatcher. The function `veryExpensiveTwo` will behave identically in a `TestCoroutineDispatcher` and
|
||
|
`Dispatchers.Default` after the thread switch for `Dispatchers.Default`. Because `withContext` always returns a value by
|
||
|
directly, there is no need to inject a `TestCoroutineDispatcher` into this function.
|
||
|
|
||
|
```kotlin
|
||
|
suspend fun veryExpensiveTwo() = withContext(Dispatchers.Default) {
|
||
|
2 // for very expensive values of 2
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Tests should provide a `TestCoroutineDispatcher` to code that calls `withContext` to provide time control for
|
||
|
delays, or when execution control is needed to test complex logic.
|
||
|
|
||
|
|
||
|
### Status of the API
|
||
|
|
||
|
This API is experimental and it is may change before migrating out of experimental (while it is marked as
|
||
|
[`@ExperimentalCoroutinesApi`][ExperimentalCoroutinesApi]).
|
||
|
Changes during experimental may have deprecation applied when possible, but it is not
|
||
|
advised to use the API in stable code before it leaves experimental due to possible breaking changes.
|
||
|
|
||
|
If you have any suggestions for improvements to this experimental API please share them them on the
|
||
|
[issue tracker](https://github.com/Kotlin/kotlinx.coroutines/issues).
|
||
|
|
||
|
<!--- MODULE kotlinx-coroutines-core -->
|
||
|
<!--- INDEX kotlinx.coroutines -->
|
||
|
[Dispatchers.Main]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-main.html
|
||
|
[CoroutineDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-dispatcher/index.html
|
||
|
[launch]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html
|
||
|
[async]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html
|
||
|
[delay]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/delay.html
|
||
|
[yield]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/yield.html
|
||
|
[CoroutineStart]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-start/index.html
|
||
|
[CoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
|
||
|
[ExperimentalCoroutinesApi]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-experimental-coroutines-api/index.html
|
||
|
<!--- MODULE kotlinx-coroutines-test -->
|
||
|
<!--- INDEX kotlinx.coroutines.test -->
|
||
|
[setMain]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/kotlinx.coroutines.-dispatchers/set-main.html
|
||
|
[runBlockingTest]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/run-blocking-test.html
|
||
|
[UncompletedCoroutinesError]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-uncompleted-coroutines-error/index.html
|
||
|
[DelayController]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-delay-controller/index.html
|
||
|
[DelayController.advanceUntilIdle]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-delay-controller/advance-until-idle.html
|
||
|
[DelayController.pauseDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-delay-controller/pause-dispatcher.html
|
||
|
[TestCoroutineDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-test-coroutine-dispatcher/index.html
|
||
|
[DelayController.resumeDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-delay-controller/resume-dispatcher.html
|
||
|
[TestCoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-test-coroutine-scope/index.html
|
||
|
[TestCoroutineExceptionHandler]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-test-coroutine-exception-handler/index.html
|
||
|
[TestCoroutineScope.cleanupTestCoroutines]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-test-coroutine-scope/cleanup-test-coroutines.html
|
||
|
[DelayController.cleanupTestCoroutines]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-delay-controller/cleanup-test-coroutines.html
|
||
|
<!--- END -->
|