Update kotlinx-coroutines-test (#2973)
This commit introduces the new version of the test module.
Please see README.md and MIGRATION.md for a thorough
discussion of the changes.
Fixes #1203
Fixes #1609
Fixes #2379
Fixes #1749
Fixes #1204
Fixes #1390
Fixes #1222
Fixes #1395
Fixes #1881
Fixes #1910
Fixes #1772
Fixes #1626
Fixes #1742
Fixes #2082
Fixes #2102
Fixes #2405
Fixes #2462
Co-authored-by: Vsevolod Tolstopyatov <[email protected]>
diff --git a/kotlinx-coroutines-test/README.md b/kotlinx-coroutines-test/README.md
index 43ae18f..54450b1 100644
--- a/kotlinx-coroutines-test/README.md
+++ b/kotlinx-coroutines-test/README.md
@@ -2,7 +2,24 @@
Test utilities for `kotlinx.coroutines`.
-This package provides testing utilities for effectively testing coroutines.
+## Overview
+
+This package provides utilities for efficiently testing coroutines.
+
+| Name | Description |
+| ---- | ----------- |
+| [runTest] | Runs the test code, automatically skipping delays and handling uncaught exceptions. |
+| [TestCoroutineScheduler] | The shared source of virtual time, used for controlling execution order and skipping delays. |
+| [TestScope] | A [CoroutineScope] that integrates with [runTest], providing access to [TestCoroutineScheduler]. |
+| [TestDispatcher] | A [CoroutineDispatcher] that whose delays are controlled by a [TestCoroutineScheduler]. |
+| [Dispatchers.setMain] | Mocks the main dispatcher using the provided one. If mocked with a [TestDispatcher], its [TestCoroutineScheduler] is used everywhere by default. |
+
+Provided [TestDispatcher] implementations:
+
+| Name | Description |
+| ---- | ----------- |
+| [StandardTestDispatcher] | A simple dispatcher with no special behavior other than being linked to a [TestCoroutineScheduler]. |
+| [UnconfinedTestDispatcher] | A dispatcher that behaves like [Dispatchers.Unconfined]. |
## Using in your project
@@ -13,24 +30,26 @@
}
```
-**Do not** depend on this project in your main sources, all utilities are intended and designed to be used only from tests.
+**Do not** depend on this project in your main sources, all utilities here 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.
+`Dispatchers.setMain` will override the `Main` dispatcher in test scenarios.
+This is helpful when one wants to execute a test in situations where the platform `Main` dispatcher is not available,
+or 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.
+On the JVM,
+the [`ServiceLoader`](https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html) mechanism is responsible
+for overwriting [Dispatchers.Main] with a testable implementation, which by default will delegate its calls to the real
+`Main` dispatcher, if any.
-You can override the `Main` implementation using [setMain][setMain] method with any [CoroutineDispatcher] implementation, e.g.:
+The `Main` implementation can be overridden using [Dispatchers.setMain][setMain] method with any [CoroutineDispatcher]
+implementation, e.g.:
```kotlin
class SomeTest {
-
+
private val mainThreadSurrogate = newSingleThreadContext("UI thread")
@Before
@@ -40,10 +59,10 @@
@After
fun tearDown() {
- Dispatchers.resetMain() // reset main dispatcher to the original Main dispatcher
+ Dispatchers.resetMain() // reset the main dispatcher to the original Main dispatcher
mainThreadSurrogate.close()
}
-
+
@Test
fun testSomeUI() = runBlocking {
launch(Dispatchers.Main) { // Will be launched in the mainThreadSurrogate dispatcher
@@ -52,372 +71,289 @@
}
}
```
-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
+Calling `setMain` or `resetMain` immediately changes the `Main` dispatcher globally.
-To test regular suspend functions or coroutines started with `launch` or `async` use the [runBlockingTest] coroutine
-builder that provides extra test control to coroutines.
+If `Main` is overridden with a [TestDispatcher], then its [TestCoroutineScheduler] is used when new [TestDispatcher] or
+[TestScope] instances are created without [TestCoroutineScheduler] being passed as an argument.
-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
+## runTest
-### Testing regular suspend functions
+[runTest] is the way to test code that involves coroutines. `suspend` functions can be called inside it.
-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.
+**IMPORTANT: in order to work with on Kotlin/JS, the result of `runTest` must be immediately `return`-ed from each test.**
+The typical invocation of [runTest] thus looks like this:
```kotlin
@Test
-fun testFoo() = runBlockingTest { // a coroutine with an extra test control
- val actual = foo()
+fun testFoo() = runTest {
+ // code under test
+}
+```
+
+In more advanced scenarios, it's possible instead to use the following form:
+```kotlin
+@Test
+fun testFoo(): TestResult {
+ // initialize some test state
+ return runTest {
+ // code under test
+ }
+}
+```
+
+[runTest] is similar to running the code with `runBlocking` on Kotlin/JVM and Kotlin/Native, or launching a new promise
+on Kotlin/JS. The main differences are the following:
+
+* **The calls to `delay` are automatically skipped**, preserving the relative execution order of the tasks. This way,
+ it's possible to make tests finish more-or-less immediately.
+* **Controlling the virtual time**: in case just skipping delays is not sufficient, it's possible to more carefully
+ guide the execution, advancing the virtual time by a duration, draining the queue of the awaiting tasks, or running
+ the tasks scheduled at the present moment.
+* **Handling uncaught exceptions** spawned in the child coroutines by throwing them at the end of the test.
+* **Waiting for asynchronous callbacks**.
+ Sometimes, especially when working with third-party code, it's impossible to mock all the dispatchers in use.
+ [runTest] will handle the situations where some code runs in dispatchers not integrated with the test module.
+
+## Delay-skipping
+
+To test regular suspend functions, which may have a delay, just run them inside the [runTest] block.
+
+```kotlin
+@Test
+fun testFoo() = runTest { // 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
+ delay(1_000) // when run in `runTest`, will finish immediately instead of delaying
// ...
}
```
-`runBlockingTest` returns `Unit` so it may be used in a single expression with common testing libraries.
+## `launch` and `async`
-### Testing `launch` or `async`
+The coroutine dispatcher used for tests is single-threaded, meaning that the child coroutines of the [runTest] block
+will run on the thread that started the test, and will never run in parallel.
-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].
+If several coroutines are waiting to be executed next, the one scheduled after the smallest delay will be chosen.
+The virtual time will automatically advance to the point of its resumption.
```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() {
+fun testWithMultipleDelays() = runTest {
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)
+ delay(1_000)
+ println("1. $currentTime") // 1000
+ delay(200)
+ println("2. $currentTime") // 1200
+ delay(2_000)
+ println("4. $currentTime") // 3200
}
+ val deferred = async {
+ delay(3_000)
+ println("3. $currentTime") // 3000
+ delay(500)
+ println("5. $currentTime") // 3500
+ }
+ deferred.await()
}
```
-*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.
+## Controlling the virtual time
-### 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.
+Inside [runTest], the following operations are supported:
+* `currentTime` gets the current virtual time.
+* `runCurrent()` runs the tasks that are scheduled at this point of virtual time.
+* `advanceUntilIdle()` runs all enqueued tasks until there are no more.
+* `advanceTimeBy(timeDelta)` runs the enqueued tasks until the current virtual time advances by `timeDelta`.
```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
- // ...
+@Test
+fun testFoo() = runTest {
+ launch {
+ println(1) // executes during runCurrent()
+ delay(1_000) // suspends until time is advanced by at least 1_000
+ println(2) // executes during advanceTimeBy(2_000)
+ delay(500) // suspends until the time is advanced by another 500 ms
+ println(3) // also executes during advanceTimeBy(2_000)
+ delay(5_000) // will suspend by another 4_500 ms
+ println(4) // executes during advanceUntilIdle()
+ }
+ // the child coroutine has not run yet
+ runCurrent()
+ // the child coroutine has called println(1), and is suspended on delay(1_000)
+ advanceTimeBy(2_000) // progress time, this will cause two calls to `delay` to resume
+ // the child coroutine has called println(2) and println(3) and suspends for another 4_500 virtual milliseconds
+ advanceUntilIdle() // will run the child coroutine to completion
+ assertEquals(6500, currentTime) // the child coroutine finished at virtual time of 6_500 milliseconds
+}
+```
+
+## Using multiple test dispatchers
+
+The virtual time is controlled by an entity called the [TestCoroutineScheduler], which behaves as the shared source of
+virtual time.
+
+Several dispatchers can be created that use the same [TestCoroutineScheduler], in which case they will share their
+knowledge of the virtual time.
+
+To access the scheduler used for this test, use the [TestScope.testScheduler] property.
+
+```kotlin
+@Test
+fun testWithMultipleDispatchers() = runTest {
+ val scheduler = testScheduler // the scheduler used for this test
+ val dispatcher1 = StandardTestDispatcher(scheduler, name = "IO dispatcher")
+ val dispatcher2 = StandardTestDispatcher(scheduler, name = "Background dispatcher")
+ launch(dispatcher1) {
+ delay(1_000)
+ println("1. $currentTime") // 1000
+ delay(200)
+ println("2. $currentTime") // 1200
+ delay(2_000)
+ println("4. $currentTime") // 3200
+ }
+ val deferred = async(dispatcher2) {
+ delay(3_000)
+ println("3. $currentTime") // 3000
+ delay(500)
+ println("5. $currentTime") // 3500
+ }
+ deferred.await()
+ }
+```
+
+**Note: if [Dispatchers.Main] is replaced by a [TestDispatcher], [runTest] will automatically use its scheduler.
+This is done so that there is no need to go through the ceremony of passing the correct scheduler to [runTest].**
+
+## Accessing the test coroutine scope
+
+Structured concurrency ties coroutines to scopes in which they are launched.
+[TestScope] is a special coroutine scope designed for testing coroutines, and a new one is automatically created
+for [runTest] and used as the receiver for the test body.
+
+However, it can be convenient to access a `CoroutineScope` before the test has started, for example, to perform mocking
+of some
+parts of the system in `@BeforeTest` via dependency injection.
+In these cases, it is possible to manually create [TestScope], the scope for the test coroutines, in advance,
+before the test begins.
+
+[TestScope] on its own does not automatically run the code launched in it.
+In addition, it is stateful in order to keep track of executing coroutines and uncaught exceptions.
+Therefore, it is important to ensure that [TestScope.runTest] is called eventually.
+
+```kotlin
+val scope = TestScope()
+
+@BeforeTest
+fun setUp() {
+ Dispatchers.setMain(StandardTestDispatcher(scope.testScheduler))
+ TestSubject.setScope(scope)
}
-fun CoroutineScope.foo(resultDeferred: Deferred<Foo>) {
+@AfterTest
+fun tearDown() {
+ Dispatchers.resetMain()
+ TestSubject.resetScope()
+}
+
+@Test
+fun testSubject() = scope.runTest {
+ // the receiver here is `testScope`
+}
+```
+
+## Eagerly entering `launch` and `async` blocks
+
+Some tests only test functionality and don't particularly care about the precise order in which coroutines are
+dispatched.
+In these cases, it can be cumbersome to always call [runCurrent] or [yield] to observe the effects of the coroutines
+after they are launched.
+
+If [runTest] executes with an [UnconfinedTestDispatcher], the child coroutines launched at the top level are entered
+*eagerly*, that is, they don't go through a dispatch until the first suspension.
+
+```kotlin
+@Test
+fun testEagerlyEnteringChildCoroutines() = runTest(UnconfinedTestDispatcher()) {
+ var entered = false
+ val deferred = CompletableDeferred<Unit>()
+ var completed = false
launch {
+ entered = true
+ deferred.await()
+ completed = true
+ }
+ assertTrue(entered) // `entered = true` already executed.
+ assertFalse(completed) // however, the child coroutine then suspended, so it is enqueued.
+ deferred.complete(Unit) // resume the coroutine.
+ assertTrue(completed) // now the child coroutine is immediately completed.
+}
+```
+
+If this behavior is desirable, but some parts of the test still require accurate dispatching, for example, to ensure
+that the code executes on the correct thread, then simply `launch` a new coroutine with the [StandardTestDispatcher].
+
+```kotlin
+@Test
+fun testEagerlyEnteringSomeChildCoroutines() = runTest(UnconfinedTestDispatcher()) {
+ var entered1 = false
+ launch {
+ entered1 = true
+ }
+ assertTrue(entered1) // `entered1 = true` already executed
+
+ var entered2 = false
+ launch(StandardTestDispatcher(testScheduler)) {
+ // this block and every coroutine launched inside it will explicitly go through the needed dispatches
+ entered2 = true
+ }
+ assertFalse(entered2)
+ runCurrent() // need to explicitly run the dispatched continuation
+ assertTrue(entered2)
+}
+```
+
+### Using `withTimeout` inside `runTest`
+
+Timeouts are also susceptible to time control, so the code below will immediately finish.
+
+```kotlin
+@Test
+fun testFooWithTimeout() = runTest {
+ assertFailsWith<TimeoutCancellationException> {
withTimeout(1_000) {
- resultDeferred.await() // await() will suspend forever waiting for uncompleted
- // ...
+ delay(999)
+ delay(2)
+ println("this won't be reached")
}
}
}
```
-*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.
+## Virtual time support with other dispatchers
-### 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.
+Calls to `withContext(Dispatchers.IO)`, `withContext(Dispatchers.Default)` ,and `withContext(Dispatchers.Main)` are
+common in coroutines-based code bases. Unfortunately, just executing code in a test will not lead to these dispatchers
+using the virtual time source, so delays will not be skipped in them.
```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) {
+suspend fun veryExpensiveFunction() = withContext(Dispatchers.Default) {
delay(1_000)
- 1 // for very expensive values of 1
+ 1
+}
+
+fun testExpensiveFunction() = runTest {
+ val result = veryExpensiveFunction() // will take a whole real-time second to execute
+ // the virtual time at this point is still 0
}
```
-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.
-
+Tests should, when possible, replace these dispatchers with a [TestDispatcher] if the `withContext` calls `delay` in the
+function under test. For example, `veryExpensiveFunction` above should allow mocking with a [TestDispatcher] using
+either dependency injection, a service locator, or a default parameter, if it is to be used with virtual time.
### Status of the API
@@ -426,36 +362,32 @@
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
+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
+[CoroutineDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-dispatcher/index.html
+[Dispatchers.Unconfined]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-unconfined.html
+[Dispatchers.Main]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-main.html
+[yield]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/yield.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 -->
+[runTest]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/run-test.html
+[TestCoroutineScheduler]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-test-coroutine-scheduler/index.html
+[TestScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-test-scope/index.html
+[TestDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-test-dispatcher/index.html
+[Dispatchers.setMain]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/set-main.html
+[StandardTestDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-standard-test-dispatcher.html
+[UnconfinedTestDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-unconfined-test-dispatcher.html
[setMain]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/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
+[TestScope.testScheduler]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-test-scope/test-scheduler.html
+[TestScope.runTest]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/run-test.html
+[runCurrent]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/run-current.html
<!--- END -->