Vsevolod Tolstopyatov | 4327b21 | 2018-12-17 19:49:12 +0300 | [diff] [blame] | 1 | # Module kotlinx-coroutines-test |
| 2 | |
Sean McQuillan | c0a8d1c | 2019-04-20 18:10:09 -0700 | [diff] [blame] | 3 | Test utilities for `kotlinx.coroutines`. |
| 4 | |
dkhalanskyjb | 2e25bae | 2021-11-19 21:02:52 +0300 | [diff] [blame] | 5 | ## Overview |
| 6 | |
| 7 | This package provides utilities for efficiently testing coroutines. |
| 8 | |
| 9 | | Name | Description | |
| 10 | | ---- | ----------- | |
| 11 | | [runTest] | Runs the test code, automatically skipping delays and handling uncaught exceptions. | |
| 12 | | [TestCoroutineScheduler] | The shared source of virtual time, used for controlling execution order and skipping delays. | |
| 13 | | [TestScope] | A [CoroutineScope] that integrates with [runTest], providing access to [TestCoroutineScheduler]. | |
NWuensche | 3b423b9 | 2022-03-09 08:57:35 +0000 | [diff] [blame] | 14 | | [TestDispatcher] | A [CoroutineDispatcher] whose delays are controlled by a [TestCoroutineScheduler]. | |
dkhalanskyjb | 2e25bae | 2021-11-19 21:02:52 +0300 | [diff] [blame] | 15 | | [Dispatchers.setMain] | Mocks the main dispatcher using the provided one. If mocked with a [TestDispatcher], its [TestCoroutineScheduler] is used everywhere by default. | |
| 16 | |
| 17 | Provided [TestDispatcher] implementations: |
| 18 | |
| 19 | | Name | Description | |
| 20 | | ---- | ----------- | |
| 21 | | [StandardTestDispatcher] | A simple dispatcher with no special behavior other than being linked to a [TestCoroutineScheduler]. | |
| 22 | | [UnconfinedTestDispatcher] | A dispatcher that behaves like [Dispatchers.Unconfined]. | |
Vsevolod Tolstopyatov | 4327b21 | 2018-12-17 19:49:12 +0300 | [diff] [blame] | 23 | |
| 24 | ## Using in your project |
| 25 | |
| 26 | Add `kotlinx-coroutines-test` to your project test dependencies: |
| 27 | ``` |
| 28 | dependencies { |
Dmitry Khalanskiy | 60d2fe8 | 2024-03-11 08:55:01 +0100 | [diff] [blame] | 29 | testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.1-Beta' |
Vsevolod Tolstopyatov | 4327b21 | 2018-12-17 19:49:12 +0300 | [diff] [blame] | 30 | } |
| 31 | ``` |
| 32 | |
dkhalanskyjb | 2e25bae | 2021-11-19 21:02:52 +0300 | [diff] [blame] | 33 | **Do not** depend on this project in your main sources, all utilities here are intended and designed to be used only from tests. |
Vsevolod Tolstopyatov | 4327b21 | 2018-12-17 19:49:12 +0300 | [diff] [blame] | 34 | |
Sean McQuillan | c0a8d1c | 2019-04-20 18:10:09 -0700 | [diff] [blame] | 35 | ## Dispatchers.Main Delegation |
Roman Elizarov | 445e798 | 2019-04-21 11:21:59 +0300 | [diff] [blame] | 36 | |
dkhalanskyjb | 2e25bae | 2021-11-19 21:02:52 +0300 | [diff] [blame] | 37 | `Dispatchers.setMain` will override the `Main` dispatcher in test scenarios. |
| 38 | This is helpful when one wants to execute a test in situations where the platform `Main` dispatcher is not available, |
| 39 | or to replace `Dispatchers.Main` with a testing dispatcher. |
Sean McQuillan | c0a8d1c | 2019-04-20 18:10:09 -0700 | [diff] [blame] | 40 | |
dkhalanskyjb | 2e25bae | 2021-11-19 21:02:52 +0300 | [diff] [blame] | 41 | On the JVM, |
| 42 | the [`ServiceLoader`](https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html) mechanism is responsible |
| 43 | for overwriting [Dispatchers.Main] with a testable implementation, which by default will delegate its calls to the real |
| 44 | `Main` dispatcher, if any. |
Vsevolod Tolstopyatov | 4327b21 | 2018-12-17 19:49:12 +0300 | [diff] [blame] | 45 | |
dkhalanskyjb | 2e25bae | 2021-11-19 21:02:52 +0300 | [diff] [blame] | 46 | The `Main` implementation can be overridden using [Dispatchers.setMain][setMain] method with any [CoroutineDispatcher] |
| 47 | implementation, e.g.: |
Vsevolod Tolstopyatov | 4327b21 | 2018-12-17 19:49:12 +0300 | [diff] [blame] | 48 | |
| 49 | ```kotlin |
| 50 | |
| 51 | class SomeTest { |
dkhalanskyjb | 2e25bae | 2021-11-19 21:02:52 +0300 | [diff] [blame] | 52 | |
Vsevolod Tolstopyatov | 4327b21 | 2018-12-17 19:49:12 +0300 | [diff] [blame] | 53 | private val mainThreadSurrogate = newSingleThreadContext("UI thread") |
| 54 | |
| 55 | @Before |
| 56 | fun setUp() { |
| 57 | Dispatchers.setMain(mainThreadSurrogate) |
| 58 | } |
| 59 | |
| 60 | @After |
| 61 | fun tearDown() { |
dkhalanskyjb | 2e25bae | 2021-11-19 21:02:52 +0300 | [diff] [blame] | 62 | Dispatchers.resetMain() // reset the main dispatcher to the original Main dispatcher |
Vsevolod Tolstopyatov | 4327b21 | 2018-12-17 19:49:12 +0300 | [diff] [blame] | 63 | mainThreadSurrogate.close() |
| 64 | } |
dkhalanskyjb | 2e25bae | 2021-11-19 21:02:52 +0300 | [diff] [blame] | 65 | |
Vsevolod Tolstopyatov | 4327b21 | 2018-12-17 19:49:12 +0300 | [diff] [blame] | 66 | @Test |
| 67 | fun testSomeUI() = runBlocking { |
| 68 | launch(Dispatchers.Main) { // Will be launched in the mainThreadSurrogate dispatcher |
Roman Elizarov | 445e798 | 2019-04-21 11:21:59 +0300 | [diff] [blame] | 69 | // ... |
Vsevolod Tolstopyatov | 4327b21 | 2018-12-17 19:49:12 +0300 | [diff] [blame] | 70 | } |
| 71 | } |
| 72 | } |
| 73 | ``` |
Sean McQuillan | c0a8d1c | 2019-04-20 18:10:09 -0700 | [diff] [blame] | 74 | |
dkhalanskyjb | 2e25bae | 2021-11-19 21:02:52 +0300 | [diff] [blame] | 75 | Calling `setMain` or `resetMain` immediately changes the `Main` dispatcher globally. |
Sean McQuillan | c0a8d1c | 2019-04-20 18:10:09 -0700 | [diff] [blame] | 76 | |
dkhalanskyjb | 2e25bae | 2021-11-19 21:02:52 +0300 | [diff] [blame] | 77 | If `Main` is overridden with a [TestDispatcher], then its [TestCoroutineScheduler] is used when new [TestDispatcher] or |
| 78 | [TestScope] instances are created without [TestCoroutineScheduler] being passed as an argument. |
Sean McQuillan | c0a8d1c | 2019-04-20 18:10:09 -0700 | [diff] [blame] | 79 | |
dkhalanskyjb | 2e25bae | 2021-11-19 21:02:52 +0300 | [diff] [blame] | 80 | ## runTest |
Sean McQuillan | c0a8d1c | 2019-04-20 18:10:09 -0700 | [diff] [blame] | 81 | |
dkhalanskyjb | 2e25bae | 2021-11-19 21:02:52 +0300 | [diff] [blame] | 82 | [runTest] is the way to test code that involves coroutines. `suspend` functions can be called inside it. |
Roman Elizarov | 445e798 | 2019-04-21 11:21:59 +0300 | [diff] [blame] | 83 | |
dkhalanskyjb | 2e25bae | 2021-11-19 21:02:52 +0300 | [diff] [blame] | 84 | **IMPORTANT: in order to work with on Kotlin/JS, the result of `runTest` must be immediately `return`-ed from each test.** |
| 85 | The typical invocation of [runTest] thus looks like this: |
Sean McQuillan | c0a8d1c | 2019-04-20 18:10:09 -0700 | [diff] [blame] | 86 | |
| 87 | ```kotlin |
| 88 | @Test |
dkhalanskyjb | 2e25bae | 2021-11-19 21:02:52 +0300 | [diff] [blame] | 89 | fun testFoo() = runTest { |
| 90 | // code under test |
| 91 | } |
| 92 | ``` |
| 93 | |
| 94 | In more advanced scenarios, it's possible instead to use the following form: |
| 95 | ```kotlin |
| 96 | @Test |
| 97 | fun testFoo(): TestResult { |
| 98 | // initialize some test state |
| 99 | return runTest { |
| 100 | // code under test |
| 101 | } |
| 102 | } |
| 103 | ``` |
| 104 | |
| 105 | [runTest] is similar to running the code with `runBlocking` on Kotlin/JVM and Kotlin/Native, or launching a new promise |
| 106 | on Kotlin/JS. The main differences are the following: |
| 107 | |
| 108 | * **The calls to `delay` are automatically skipped**, preserving the relative execution order of the tasks. This way, |
| 109 | it's possible to make tests finish more-or-less immediately. |
Dmitry Khalanskiy | cb0ef71 | 2023-02-27 18:04:23 +0300 | [diff] [blame] | 110 | * **The execution times out after 10 seconds**, cancelling the test coroutine to prevent tests from hanging forever |
| 111 | and eating up the CI resources. |
dkhalanskyjb | 2e25bae | 2021-11-19 21:02:52 +0300 | [diff] [blame] | 112 | * **Controlling the virtual time**: in case just skipping delays is not sufficient, it's possible to more carefully |
| 113 | guide the execution, advancing the virtual time by a duration, draining the queue of the awaiting tasks, or running |
| 114 | the tasks scheduled at the present moment. |
| 115 | * **Handling uncaught exceptions** spawned in the child coroutines by throwing them at the end of the test. |
| 116 | * **Waiting for asynchronous callbacks**. |
| 117 | Sometimes, especially when working with third-party code, it's impossible to mock all the dispatchers in use. |
| 118 | [runTest] will handle the situations where some code runs in dispatchers not integrated with the test module. |
| 119 | |
Dmitry Khalanskiy | cb0ef71 | 2023-02-27 18:04:23 +0300 | [diff] [blame] | 120 | ## Timeout |
| 121 | |
| 122 | Test automatically time out after 10 seconds. For example, this test will fail with a timeout exception: |
| 123 | |
| 124 | ```kotlin |
| 125 | @Test |
| 126 | fun testHanging() = runTest { |
| 127 | CompletableDeferred<Unit>().await() // will hang forever |
| 128 | } |
| 129 | ``` |
| 130 | |
| 131 | In case the test is expected to take longer than 10 seconds, the timeout can be increased by passing the `timeout` |
| 132 | parameter: |
| 133 | |
| 134 | ```kotlin |
| 135 | @Test |
| 136 | fun testTakingALongTime() = runTest(timeout = 30.seconds) { |
| 137 | val result = withContext(Dispatchers.Default) { |
| 138 | delay(20.seconds) // this delay is not in the test dispatcher and will not be skipped |
| 139 | 3 |
| 140 | } |
| 141 | assertEquals(3, result) |
| 142 | } |
| 143 | ``` |
| 144 | |
dkhalanskyjb | 2e25bae | 2021-11-19 21:02:52 +0300 | [diff] [blame] | 145 | ## Delay-skipping |
| 146 | |
| 147 | To test regular suspend functions, which may have a delay, just run them inside the [runTest] block. |
| 148 | |
| 149 | ```kotlin |
| 150 | @Test |
| 151 | fun testFoo() = runTest { // a coroutine with an extra test control |
| 152 | val actual = foo() |
Roman Elizarov | 445e798 | 2019-04-21 11:21:59 +0300 | [diff] [blame] | 153 | // ... |
Sean McQuillan | c0a8d1c | 2019-04-20 18:10:09 -0700 | [diff] [blame] | 154 | } |
| 155 | |
| 156 | suspend fun foo() { |
dkhalanskyjb | 2e25bae | 2021-11-19 21:02:52 +0300 | [diff] [blame] | 157 | delay(1_000) // when run in `runTest`, will finish immediately instead of delaying |
Roman Elizarov | 445e798 | 2019-04-21 11:21:59 +0300 | [diff] [blame] | 158 | // ... |
Sean McQuillan | c0a8d1c | 2019-04-20 18:10:09 -0700 | [diff] [blame] | 159 | } |
| 160 | ``` |
| 161 | |
dkhalanskyjb | 2e25bae | 2021-11-19 21:02:52 +0300 | [diff] [blame] | 162 | ## `launch` and `async` |
Sean McQuillan | c0a8d1c | 2019-04-20 18:10:09 -0700 | [diff] [blame] | 163 | |
dkhalanskyjb | 2e25bae | 2021-11-19 21:02:52 +0300 | [diff] [blame] | 164 | The coroutine dispatcher used for tests is single-threaded, meaning that the child coroutines of the [runTest] block |
| 165 | will run on the thread that started the test, and will never run in parallel. |
Sean McQuillan | c0a8d1c | 2019-04-20 18:10:09 -0700 | [diff] [blame] | 166 | |
dkhalanskyjb | 2e25bae | 2021-11-19 21:02:52 +0300 | [diff] [blame] | 167 | If several coroutines are waiting to be executed next, the one scheduled after the smallest delay will be chosen. |
| 168 | The virtual time will automatically advance to the point of its resumption. |
Sean McQuillan | c0a8d1c | 2019-04-20 18:10:09 -0700 | [diff] [blame] | 169 | |
| 170 | ```kotlin |
| 171 | @Test |
dkhalanskyjb | 2e25bae | 2021-11-19 21:02:52 +0300 | [diff] [blame] | 172 | fun testWithMultipleDelays() = runTest { |
Sean McQuillan | c0a8d1c | 2019-04-20 18:10:09 -0700 | [diff] [blame] | 173 | launch { |
dkhalanskyjb | 2e25bae | 2021-11-19 21:02:52 +0300 | [diff] [blame] | 174 | delay(1_000) |
| 175 | println("1. $currentTime") // 1000 |
| 176 | delay(200) |
| 177 | println("2. $currentTime") // 1200 |
| 178 | delay(2_000) |
| 179 | println("4. $currentTime") // 3200 |
Sean McQuillan | c0a8d1c | 2019-04-20 18:10:09 -0700 | [diff] [blame] | 180 | } |
dkhalanskyjb | 2e25bae | 2021-11-19 21:02:52 +0300 | [diff] [blame] | 181 | val deferred = async { |
| 182 | delay(3_000) |
| 183 | println("3. $currentTime") // 3000 |
| 184 | delay(500) |
| 185 | println("5. $currentTime") // 3500 |
| 186 | } |
| 187 | deferred.await() |
Sean McQuillan | c0a8d1c | 2019-04-20 18:10:09 -0700 | [diff] [blame] | 188 | } |
| 189 | ``` |
| 190 | |
dkhalanskyjb | 2e25bae | 2021-11-19 21:02:52 +0300 | [diff] [blame] | 191 | ## Controlling the virtual time |
Sean McQuillan | c0a8d1c | 2019-04-20 18:10:09 -0700 | [diff] [blame] | 192 | |
Dmitry Khalanskiy | cb0ef71 | 2023-02-27 18:04:23 +0300 | [diff] [blame] | 193 | Inside [runTest], the execution is scheduled by [TestCoroutineScheduler], which is a virtual time scheduler. |
| 194 | The scheduler has several special methods that allow controlling the virtual time: |
dkhalanskyjb | 2e25bae | 2021-11-19 21:02:52 +0300 | [diff] [blame] | 195 | * `currentTime` gets the current virtual time. |
| 196 | * `runCurrent()` runs the tasks that are scheduled at this point of virtual time. |
| 197 | * `advanceUntilIdle()` runs all enqueued tasks until there are no more. |
| 198 | * `advanceTimeBy(timeDelta)` runs the enqueued tasks until the current virtual time advances by `timeDelta`. |
Dmitry Khalanskiy | cb0ef71 | 2023-02-27 18:04:23 +0300 | [diff] [blame] | 199 | * `timeSource` returns a `TimeSource` that uses the virtual time. |
Sean McQuillan | c0a8d1c | 2019-04-20 18:10:09 -0700 | [diff] [blame] | 200 | |
| 201 | ```kotlin |
dkhalanskyjb | 2e25bae | 2021-11-19 21:02:52 +0300 | [diff] [blame] | 202 | @Test |
| 203 | fun testFoo() = runTest { |
| 204 | launch { |
Dmitry Khalanskiy | cb0ef71 | 2023-02-27 18:04:23 +0300 | [diff] [blame] | 205 | val workDuration = testScheduler.timeSource.measureTime { |
| 206 | println(1) // executes during runCurrent() |
| 207 | delay(1_000) // suspends until time is advanced by at least 1_000 |
| 208 | println(2) // executes during advanceTimeBy(2_000) |
| 209 | delay(500) // suspends until the time is advanced by another 500 ms |
| 210 | println(3) // also executes during advanceTimeBy(2_000) |
| 211 | delay(5_000) // will suspend by another 4_500 ms |
| 212 | println(4) // executes during advanceUntilIdle() |
| 213 | } |
| 214 | assertEquals(6500.milliseconds, workDuration) // the work took 6_500 ms of virtual time |
dkhalanskyjb | 2e25bae | 2021-11-19 21:02:52 +0300 | [diff] [blame] | 215 | } |
| 216 | // the child coroutine has not run yet |
Dmitry Khalanskiy | cb0ef71 | 2023-02-27 18:04:23 +0300 | [diff] [blame] | 217 | testScheduler.runCurrent() |
dkhalanskyjb | 2e25bae | 2021-11-19 21:02:52 +0300 | [diff] [blame] | 218 | // the child coroutine has called println(1), and is suspended on delay(1_000) |
Dmitry Khalanskiy | cb0ef71 | 2023-02-27 18:04:23 +0300 | [diff] [blame] | 219 | testScheduler.advanceTimeBy(2.seconds) // progress time, this will cause two calls to `delay` to resume |
dkhalanskyjb | 2e25bae | 2021-11-19 21:02:52 +0300 | [diff] [blame] | 220 | // the child coroutine has called println(2) and println(3) and suspends for another 4_500 virtual milliseconds |
Dmitry Khalanskiy | cb0ef71 | 2023-02-27 18:04:23 +0300 | [diff] [blame] | 221 | testScheduler.advanceUntilIdle() // will run the child coroutine to completion |
dkhalanskyjb | 2e25bae | 2021-11-19 21:02:52 +0300 | [diff] [blame] | 222 | assertEquals(6500, currentTime) // the child coroutine finished at virtual time of 6_500 milliseconds |
| 223 | } |
| 224 | ``` |
| 225 | |
| 226 | ## Using multiple test dispatchers |
| 227 | |
| 228 | The virtual time is controlled by an entity called the [TestCoroutineScheduler], which behaves as the shared source of |
| 229 | virtual time. |
| 230 | |
| 231 | Several dispatchers can be created that use the same [TestCoroutineScheduler], in which case they will share their |
| 232 | knowledge of the virtual time. |
| 233 | |
| 234 | To access the scheduler used for this test, use the [TestScope.testScheduler] property. |
| 235 | |
| 236 | ```kotlin |
| 237 | @Test |
| 238 | fun testWithMultipleDispatchers() = runTest { |
| 239 | val scheduler = testScheduler // the scheduler used for this test |
| 240 | val dispatcher1 = StandardTestDispatcher(scheduler, name = "IO dispatcher") |
| 241 | val dispatcher2 = StandardTestDispatcher(scheduler, name = "Background dispatcher") |
| 242 | launch(dispatcher1) { |
| 243 | delay(1_000) |
| 244 | println("1. $currentTime") // 1000 |
| 245 | delay(200) |
| 246 | println("2. $currentTime") // 1200 |
| 247 | delay(2_000) |
| 248 | println("4. $currentTime") // 3200 |
| 249 | } |
| 250 | val deferred = async(dispatcher2) { |
| 251 | delay(3_000) |
| 252 | println("3. $currentTime") // 3000 |
| 253 | delay(500) |
| 254 | println("5. $currentTime") // 3500 |
| 255 | } |
| 256 | deferred.await() |
| 257 | } |
| 258 | ``` |
| 259 | |
| 260 | **Note: if [Dispatchers.Main] is replaced by a [TestDispatcher], [runTest] will automatically use its scheduler. |
| 261 | This is done so that there is no need to go through the ceremony of passing the correct scheduler to [runTest].** |
| 262 | |
| 263 | ## Accessing the test coroutine scope |
| 264 | |
| 265 | Structured concurrency ties coroutines to scopes in which they are launched. |
| 266 | [TestScope] is a special coroutine scope designed for testing coroutines, and a new one is automatically created |
| 267 | for [runTest] and used as the receiver for the test body. |
| 268 | |
| 269 | However, it can be convenient to access a `CoroutineScope` before the test has started, for example, to perform mocking |
| 270 | of some |
| 271 | parts of the system in `@BeforeTest` via dependency injection. |
| 272 | In these cases, it is possible to manually create [TestScope], the scope for the test coroutines, in advance, |
| 273 | before the test begins. |
| 274 | |
| 275 | [TestScope] on its own does not automatically run the code launched in it. |
| 276 | In addition, it is stateful in order to keep track of executing coroutines and uncaught exceptions. |
| 277 | Therefore, it is important to ensure that [TestScope.runTest] is called eventually. |
| 278 | |
| 279 | ```kotlin |
| 280 | val scope = TestScope() |
| 281 | |
| 282 | @BeforeTest |
| 283 | fun setUp() { |
| 284 | Dispatchers.setMain(StandardTestDispatcher(scope.testScheduler)) |
| 285 | TestSubject.setScope(scope) |
Sean McQuillan | c0a8d1c | 2019-04-20 18:10:09 -0700 | [diff] [blame] | 286 | } |
| 287 | |
dkhalanskyjb | 2e25bae | 2021-11-19 21:02:52 +0300 | [diff] [blame] | 288 | @AfterTest |
| 289 | fun tearDown() { |
| 290 | Dispatchers.resetMain() |
| 291 | TestSubject.resetScope() |
| 292 | } |
| 293 | |
| 294 | @Test |
| 295 | fun testSubject() = scope.runTest { |
| 296 | // the receiver here is `testScope` |
| 297 | } |
| 298 | ``` |
| 299 | |
Dmitry Khalanskiy | cb0ef71 | 2023-02-27 18:04:23 +0300 | [diff] [blame] | 300 | ## Running background work |
| 301 | |
| 302 | Sometimes, the fact that [runTest] waits for all the coroutines to finish is undesired. |
| 303 | For example, the system under test may need to receive data from coroutines that always run in the background. |
| 304 | Emulating such coroutines by launching them from the test body is not sufficient, because [runTest] will wait for them |
| 305 | to finish, which they never typically do. |
| 306 | |
| 307 | For these cases, there is a special coroutine scope: [TestScope.backgroundScope]. |
| 308 | Coroutines launched in it will be cancelled at the end of the test. |
| 309 | |
| 310 | ```kotlin |
| 311 | @Test |
| 312 | fun testExampleBackgroundJob() = runTest { |
| 313 | val channel = Channel<Int>() |
| 314 | backgroundScope.launch { |
| 315 | var i = 0 |
| 316 | while (true) { |
| 317 | channel.send(i++) |
| 318 | } |
| 319 | } |
| 320 | repeat(100) { |
| 321 | assertEquals(it, channel.receive()) |
| 322 | } |
| 323 | } |
| 324 | ``` |
| 325 | |
dkhalanskyjb | 2e25bae | 2021-11-19 21:02:52 +0300 | [diff] [blame] | 326 | ## Eagerly entering `launch` and `async` blocks |
| 327 | |
| 328 | Some tests only test functionality and don't particularly care about the precise order in which coroutines are |
| 329 | dispatched. |
| 330 | In these cases, it can be cumbersome to always call [runCurrent] or [yield] to observe the effects of the coroutines |
| 331 | after they are launched. |
| 332 | |
| 333 | If [runTest] executes with an [UnconfinedTestDispatcher], the child coroutines launched at the top level are entered |
| 334 | *eagerly*, that is, they don't go through a dispatch until the first suspension. |
| 335 | |
| 336 | ```kotlin |
| 337 | @Test |
| 338 | fun testEagerlyEnteringChildCoroutines() = runTest(UnconfinedTestDispatcher()) { |
| 339 | var entered = false |
| 340 | val deferred = CompletableDeferred<Unit>() |
| 341 | var completed = false |
Sean McQuillan | c0a8d1c | 2019-04-20 18:10:09 -0700 | [diff] [blame] | 342 | launch { |
dkhalanskyjb | 2e25bae | 2021-11-19 21:02:52 +0300 | [diff] [blame] | 343 | entered = true |
| 344 | deferred.await() |
| 345 | completed = true |
| 346 | } |
| 347 | assertTrue(entered) // `entered = true` already executed. |
| 348 | assertFalse(completed) // however, the child coroutine then suspended, so it is enqueued. |
| 349 | deferred.complete(Unit) // resume the coroutine. |
| 350 | assertTrue(completed) // now the child coroutine is immediately completed. |
| 351 | } |
| 352 | ``` |
| 353 | |
| 354 | If this behavior is desirable, but some parts of the test still require accurate dispatching, for example, to ensure |
| 355 | that the code executes on the correct thread, then simply `launch` a new coroutine with the [StandardTestDispatcher]. |
| 356 | |
| 357 | ```kotlin |
| 358 | @Test |
| 359 | fun testEagerlyEnteringSomeChildCoroutines() = runTest(UnconfinedTestDispatcher()) { |
| 360 | var entered1 = false |
| 361 | launch { |
| 362 | entered1 = true |
| 363 | } |
| 364 | assertTrue(entered1) // `entered1 = true` already executed |
| 365 | |
| 366 | var entered2 = false |
| 367 | launch(StandardTestDispatcher(testScheduler)) { |
| 368 | // this block and every coroutine launched inside it will explicitly go through the needed dispatches |
| 369 | entered2 = true |
| 370 | } |
| 371 | assertFalse(entered2) |
| 372 | runCurrent() // need to explicitly run the dispatched continuation |
| 373 | assertTrue(entered2) |
| 374 | } |
| 375 | ``` |
| 376 | |
| 377 | ### Using `withTimeout` inside `runTest` |
| 378 | |
| 379 | Timeouts are also susceptible to time control, so the code below will immediately finish. |
| 380 | |
| 381 | ```kotlin |
| 382 | @Test |
| 383 | fun testFooWithTimeout() = runTest { |
| 384 | assertFailsWith<TimeoutCancellationException> { |
Sean McQuillan | c0a8d1c | 2019-04-20 18:10:09 -0700 | [diff] [blame] | 385 | withTimeout(1_000) { |
dkhalanskyjb | 2e25bae | 2021-11-19 21:02:52 +0300 | [diff] [blame] | 386 | delay(999) |
| 387 | delay(2) |
| 388 | println("this won't be reached") |
Sean McQuillan | c0a8d1c | 2019-04-20 18:10:09 -0700 | [diff] [blame] | 389 | } |
| 390 | } |
| 391 | } |
| 392 | ``` |
| 393 | |
dkhalanskyjb | 2e25bae | 2021-11-19 21:02:52 +0300 | [diff] [blame] | 394 | ## Virtual time support with other dispatchers |
Sean McQuillan | c0a8d1c | 2019-04-20 18:10:09 -0700 | [diff] [blame] | 395 | |
dkhalanskyjb | 2e25bae | 2021-11-19 21:02:52 +0300 | [diff] [blame] | 396 | Calls to `withContext(Dispatchers.IO)`, `withContext(Dispatchers.Default)` ,and `withContext(Dispatchers.Main)` are |
| 397 | common in coroutines-based code bases. Unfortunately, just executing code in a test will not lead to these dispatchers |
| 398 | using the virtual time source, so delays will not be skipped in them. |
Sean McQuillan | c0a8d1c | 2019-04-20 18:10:09 -0700 | [diff] [blame] | 399 | |
| 400 | ```kotlin |
dkhalanskyjb | 2e25bae | 2021-11-19 21:02:52 +0300 | [diff] [blame] | 401 | suspend fun veryExpensiveFunction() = withContext(Dispatchers.Default) { |
Sean McQuillan | c0a8d1c | 2019-04-20 18:10:09 -0700 | [diff] [blame] | 402 | delay(1_000) |
dkhalanskyjb | 2e25bae | 2021-11-19 21:02:52 +0300 | [diff] [blame] | 403 | 1 |
| 404 | } |
| 405 | |
| 406 | fun testExpensiveFunction() = runTest { |
| 407 | val result = veryExpensiveFunction() // will take a whole real-time second to execute |
| 408 | // the virtual time at this point is still 0 |
Sean McQuillan | c0a8d1c | 2019-04-20 18:10:09 -0700 | [diff] [blame] | 409 | } |
| 410 | ``` |
| 411 | |
dkhalanskyjb | 2e25bae | 2021-11-19 21:02:52 +0300 | [diff] [blame] | 412 | Tests should, when possible, replace these dispatchers with a [TestDispatcher] if the `withContext` calls `delay` in the |
| 413 | function under test. For example, `veryExpensiveFunction` above should allow mocking with a [TestDispatcher] using |
| 414 | either dependency injection, a service locator, or a default parameter, if it is to be used with virtual time. |
Sean McQuillan | c0a8d1c | 2019-04-20 18:10:09 -0700 | [diff] [blame] | 415 | |
| 416 | ### Status of the API |
| 417 | |
Dmitry Khalanskiy | cb0ef71 | 2023-02-27 18:04:23 +0300 | [diff] [blame] | 418 | Many parts of the API is experimental, and it is may change before migrating out of experimental (while it is marked as |
Roman Elizarov | 445e798 | 2019-04-21 11:21:59 +0300 | [diff] [blame] | 419 | [`@ExperimentalCoroutinesApi`][ExperimentalCoroutinesApi]). |
| 420 | Changes during experimental may have deprecation applied when possible, but it is not |
| 421 | advised to use the API in stable code before it leaves experimental due to possible breaking changes. |
Sean McQuillan | c0a8d1c | 2019-04-20 18:10:09 -0700 | [diff] [blame] | 422 | |
Mohammad Ershad Nasri | 774e66d | 2022-04-11 01:10:54 -0700 | [diff] [blame] | 423 | If you have any suggestions for improvements to this experimental API please share them on the |
Sean McQuillan | c0a8d1c | 2019-04-20 18:10:09 -0700 | [diff] [blame] | 424 | [issue tracker](https://github.com/Kotlin/kotlinx.coroutines/issues). |
| 425 | |
Vsevolod Tolstopyatov | 4327b21 | 2018-12-17 19:49:12 +0300 | [diff] [blame] | 426 | <!--- MODULE kotlinx-coroutines-core --> |
| 427 | <!--- INDEX kotlinx.coroutines --> |
Vsevolod Tolstopyatov | 167c44e | 2020-11-30 05:36:23 -0800 | [diff] [blame] | 428 | |
zoobestik | 74cf9ea | 2022-06-23 16:51:33 +0300 | [diff] [blame] | 429 | [CoroutineScope]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html |
| 430 | [CoroutineDispatcher]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-dispatcher/index.html |
| 431 | [Dispatchers.Unconfined]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-unconfined.html |
| 432 | [Dispatchers.Main]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-main.html |
| 433 | [yield]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/yield.html |
| 434 | [ExperimentalCoroutinesApi]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-experimental-coroutines-api/index.html |
Vsevolod Tolstopyatov | 167c44e | 2020-11-30 05:36:23 -0800 | [diff] [blame] | 435 | |
Vsevolod Tolstopyatov | 4327b21 | 2018-12-17 19:49:12 +0300 | [diff] [blame] | 436 | <!--- MODULE kotlinx-coroutines-test --> |
| 437 | <!--- INDEX kotlinx.coroutines.test --> |
Vsevolod Tolstopyatov | 167c44e | 2020-11-30 05:36:23 -0800 | [diff] [blame] | 438 | |
zoobestik | 74cf9ea | 2022-06-23 16:51:33 +0300 | [diff] [blame] | 439 | [runTest]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/run-test.html |
| 440 | [TestCoroutineScheduler]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-test-coroutine-scheduler/index.html |
| 441 | [TestScope]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-test-scope/index.html |
| 442 | [TestDispatcher]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-test-dispatcher/index.html |
| 443 | [Dispatchers.setMain]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/set-main.html |
| 444 | [StandardTestDispatcher]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-standard-test-dispatcher.html |
| 445 | [UnconfinedTestDispatcher]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-unconfined-test-dispatcher.html |
| 446 | [setMain]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/set-main.html |
| 447 | [TestScope.testScheduler]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-test-scope/test-scheduler.html |
| 448 | [TestScope.runTest]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/run-test.html |
Dmitry Khalanskiy | cb0ef71 | 2023-02-27 18:04:23 +0300 | [diff] [blame] | 449 | [TestScope.backgroundScope]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-test-scope/background-scope.html |
zoobestik | 74cf9ea | 2022-06-23 16:51:33 +0300 | [diff] [blame] | 450 | [runCurrent]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/run-current.html |
Vsevolod Tolstopyatov | 167c44e | 2020-11-30 05:36:23 -0800 | [diff] [blame] | 451 | |
Vsevolod Tolstopyatov | 4327b21 | 2018-12-17 19:49:12 +0300 | [diff] [blame] | 452 | <!--- END --> |