1# Migration to the new kotlinx-coroutines-test API 2 3In version 1.6.0, the API of the test module changed significantly. 4This is a guide for gradually adapting the existing test code to the new API. 5This guide is written step-by-step; the idea is to separate the migration into several sets of small changes. 6 7## Remove custom `UncaughtExceptionCaptor`, `DelayController`, and `TestCoroutineScope` implementations 8 9We couldn't find any code that defined new implementations of these interfaces, so they are deprecated. It's likely that 10you don't need to do anything for this section. 11 12### `UncaughtExceptionCaptor` 13 14If the code base has an `UncaughtExceptionCaptor`, its special behavior as opposed to just `CoroutineExceptionHandler` 15was that, at the end of `runBlockingTest` or `cleanupTestCoroutines` (or both), its `cleanupTestCoroutines` procedure 16was called. 17 18We currently don't provide a replacement for this. 19However, `runTest` follows structured concurrency better than `runBlockingTest` did, so exceptions from child coroutines 20are propagated structurally, which makes uncaught exception handlers less useful. 21 22If you have a use case for this, please tell us about it at the issue tracker. 23Meanwhile, it should be possible to use a custom exception captor, which should only implement 24`CoroutineExceptionHandler` now, like this: 25 26```kotlin 27@Test 28fun testFoo() = runTest { 29 val customCaptor = MyUncaughtExceptionCaptor() 30 launch(customCaptor) { 31 // ... 32 } 33 advanceUntilIdle() 34 customCaptor.cleanupTestCoroutines() 35} 36``` 37 38### `DelayController` 39 40We don't provide a way to define custom dispatching strategies that support virtual time. 41That said, we significantly enhanced this mechanism: 42* Using multiple test dispatchers simultaneously is supported. 43 For the dispatchers to have a shared knowledge of the virtual time, either the same `TestCoroutineScheduler` should be 44 passed to each of them, or all of them should be constructed after `Dispatchers.setMain` is called with some test 45 dispatcher. 46* Both a simple `StandardTestDispatcher` that is always paused, and unconfined `UnconfinedTestDispatcher` are provided. 47 48If you have a use case for `DelayController` that's not covered by what we provide, please tell us about it in the issue 49tracker. 50 51### `TestCoroutineScope` 52 53This scope couldn't be meaningfully used in tandem with `runBlockingTest`: according to the definition of 54`TestCoroutineScope.runBlockingTest`, only the scope's `coroutineContext` is used. 55So, there could be two reasons for defining a custom implementation: 56 57* Avoiding the restrictions on placed `coroutineContext` in the `TestCoroutineScope` constructor function. 58 These restrictions consisted of requirements for `CoroutineExceptionHandler` being an `UncaughtExceptionCaptor`, and 59 `ContinuationInterceptor` being a `DelayController`, so it is also possible to fulfill these restrictions by defining 60 conforming instances. In this case, follow the instructions about replacing them. 61* Using without `runBlockingTest`. In this case, you don't even need to implement `TestCoroutineScope`: nothing else 62 accepts a `TestCoroutineScope` specifically as an argument. 63 64## Remove usages of `TestCoroutineExceptionHandler` and `TestCoroutineScope.uncaughtExceptions` 65 66It is already illegal to use a `TestCoroutineScope` without performing `cleanupTestCoroutines`, so the valid uses of 67`TestCoroutineExceptionHandler` include: 68 69* Accessing `uncaughtExceptions` in the middle of the test to make sure that there weren't any uncaught exceptions 70 *yet*. 71 If there are any, they will be thrown by the cleanup procedure anyway. 72 We don't support this use case, given how comparatively rare it is, but it can be handled in the same way as the 73 following one. 74* Accessing `uncaughtExceptions` when the uncaught exceptions are actually expected. 75 In this case, `cleanupTestCoroutines` will fail with an exception that is being caught later. 76 It would be better in this case to use a custom `CoroutineExceptionHandler` so that actual problems that could be 77 found by the cleanup procedure are not superseded by the exceptions that are expected. 78 An example is shown below. 79 80```kotlin 81val exceptions = mutableListOf<Throwable>() 82val customCaptor = CoroutineExceptionHandler { ctx, throwable -> 83 exceptions.add(throwable) // add proper synchronization if the test is multithreaded 84} 85 86@Test 87fun testFoo() = runTest { 88 launch(customCaptor) { 89 // ... 90 } 91 advanceUntilIdle() 92 // check the list of the caught exceptions 93} 94``` 95 96## Auto-replace `TestCoroutineScope` constructor function with `createTestCoroutineScope` 97 98This should not break anything, as `TestCoroutineScope` is now defined in terms of `createTestCoroutineScope`. 99If it does break something, it means that you already supplied a `TestCoroutineScheduler` to some scope; in this case, 100also pass this scheduler as the argument to the dispatcher. 101 102## Replace usages of `pauseDispatcher` and `resumeDispatcher` with a `StandardTestDispatcher` 103 104* In places where `pauseDispatcher` in its block form is called, replace it with a call to 105 `withContext(StandardTestDispatcher(testScheduler))` 106 (`testScheduler` is available as a field of `TestCoroutineScope`, 107 or `scheduler` is available as a field of `TestCoroutineDispatcher`), 108 followed by `advanceUntilIdle()`. 109 This is not an automatic replacement, as there can be tricky situations where the test dispatcher is already paused 110 when `pauseDispatcher { X }` is called. In such cases, simply replace `pauseDispatcher { X }` with `X`. 111* Often, `pauseDispatcher()` in a non-block form is used at the start of the test. 112 Then, attempt to remove `TestCoroutineDispatcher` from the arguments to `createTestCoroutineScope`, 113 if a standalone `TestCoroutineScope` or the `scope.runBlockingTest` form is used, 114 or pass a `StandardTestDispatcher` as an argument to `runBlockingTest`. 115 This will lead to the test using a `StandardTestDispatcher`, which does not allow pausing and resuming, 116 instead of the deprecated `TestCoroutineDispatcher`. 117* Sometimes, `pauseDispatcher()` and `resumeDispatcher()` are employed used throughout the test. 118 In this case, attempt to wrap everything until the next `resumeDispatcher()` in 119 a `withContext(StandardTestDispatcher(testScheduler))` block, or try using some other combinations of 120 `StandardTestDispatcher` (where dispatches are needed) and `UnconfinedTestDispatcher` (where it isn't important where 121 execution happens). 122 123## Replace `advanceTimeBy(n)` with `advanceTimeBy(n); runCurrent()` 124 125For `TestCoroutineScope` and `DelayController`, the `advanceTimeBy` method is deprecated. 126It is not deprecated for `TestCoroutineScheduler` and `TestScope`, but has a different meaning: it does not run the 127tasks scheduled *at* `currentTime + n`. 128 129There is an automatic replacement for this deprecation, which produces correct but inelegant code. 130 131Alternatively, you can wait until replacing `TestCoroutineScope` with `TestScope`: it's possible that you will not 132encounter this edge case. 133 134## Replace `runBlockingTest` with `runTest(UnconfinedTestDispatcher())` 135 136This is a major change, affecting many things, and can be done in parallel with replacing `TestCoroutineScope` with 137`TestScope`. 138 139Significant differences of `runTest` from `runBlockingTest` are each given a section below. 140 141### It works properly with other dispatchers and asynchronous completions. 142 143No action on your part is required, other than replacing `runBlocking` with `runTest` as well. 144 145### It uses `StandardTestDispatcher` by default, not `TestCoroutineDispatcher`. 146 147By now, calls to `pauseDispatcher` and `resumeDispatcher` should be purged from the code base, so only the unpaused 148variant of `TestCoroutineDispatcher` should be used. 149This version of the dispatcher has the property of eagerly entering `launch` and `async` blocks: 150code until the first suspension is executed without dispatching. 151 152There are two common ways in which this property is useful. 153 154#### `TestCoroutineDispatcher` for the top-level coroutine 155 156Some tests that rely on `launch` and `async` blocks being entered immediately have a form similar to this: 157```kotlin 158runTest(TestCoroutineDispatcher()) { 159 launch { 160 updateSomething() 161 } 162 checkThatSomethingWasUpdated() 163 launch { 164 updateSomethingElse() 165 } 166 checkThatSomethingElseWasUpdated() 167} 168``` 169 170If the `TestCoroutineDispatcher()` is simply removed, `StandardTestDispatcher()` will be used, which will cause 171the test to fail. 172 173In these cases, `UnconfinedTestDispatcher()` should be used. 174We ensured that, when run with an `UnconfinedTestDispatcher`, `runTest` also eagerly enters `launch` and `async` 175blocks. 176 177Note though that *this only works at the top level*: if a child coroutine also called `launch` or `async`, we don't provide 178any guarantees about their dispatching order. 179 180#### `TestCoroutineDispatcher` for testing intermediate emissions 181 182Some code tests `StateFlow` or channels in a manner similar to this: 183 184```kotlin 185@Test 186fun testAllEmissions() = runTest(TestCoroutineDispatcher()) { 187 val values = mutableListOf<Int>() 188 val stateFlow = MutableStateFlow(0) 189 val job = launch { 190 stateFlow.collect { 191 values.add(it) 192 } 193 } 194 stateFlow.value = 1 195 stateFlow.value = 2 196 stateFlow.value = 3 197 job.cancel() 198 // each assignment will immediately resume the collecting child coroutine, 199 // so no values will be skipped. 200 assertEquals(listOf(0, 1, 2, 3), values) 201} 202``` 203 204Such code will fail when `TestCoroutineDispatcher()` is not used: not every emission will be listed. 205In this particular case, none will be listed at all. 206 207The reason for this is that setting `stateFlow.value` (as is sending to a channel, as are some other things) wakes up 208the coroutine waiting for the new value, but *typically* does not immediately run the collecting code, instead simply 209dispatching it. 210The exceptions are the coroutines running in dispatchers that don't (always) go through a dispatch, 211`Dispatchers.Unconfined`, `Dispatchers.Main.immediate`, `UnconfinedTestDispatcher`, or `TestCoroutineDispatcher` in 212the unpaused state. 213 214Therefore, a solution is to launch the collection in an unconfined dispatcher: 215 216```kotlin 217@Test 218fun testAllEmissions() = runTest { 219 val values = mutableListOf<Int>() 220 val stateFlow = MutableStateFlow(0) 221 val job = launch(UnconfinedTestDispatcher(testScheduler)) { // <------ 222 stateFlow.collect { 223 values.add(it) 224 } 225 } 226 stateFlow.value = 1 227 stateFlow.value = 2 228 stateFlow.value = 3 229 job.cancel() 230 // each assignment will immediately resume the collecting child coroutine, 231 // so no values will be skipped. 232 assertEquals(listOf(0, 1, 2, 3), values) 233} 234``` 235 236Note that `testScheduler` is passed so that the unconfined dispatcher is linked to `runTest`. 237Also, note that `UnconfinedTestDispatcher` is not passed to `runTest`. 238This is due to the fact that, *inside* the `UnconfinedTestDispatcher`, there are no execution order guarantees, 239so it would not be guaranteed that setting `stateFlow.value` would immediately run the collecting code 240(though in this case, it does). 241 242#### Other considerations 243 244Using `UnconfinedTestDispatcher` as an argument to `runTest` will probably lead to the test being executed as it 245did, but it's still possible that the test relies on the specific dispatching order of `TestCoroutineDispatcher`, 246so it will need to be tweaked. 247 248If some code is expected to have run at some point, but it hasn't, use `runCurrent` to force the tasks scheduled 249at this moment of time to run. 250For example, the `StateFlow` example above can also be forced to succeed by doing this: 251 252```kotlin 253@Test 254fun testAllEmissions() = runTest { 255 val values = mutableListOf<Int>() 256 val stateFlow = MutableStateFlow(0) 257 val job = launch { 258 stateFlow.collect { 259 values.add(it) 260 } 261 } 262 runCurrent() 263 stateFlow.value = 1 264 runCurrent() 265 stateFlow.value = 2 266 runCurrent() 267 stateFlow.value = 3 268 runCurrent() 269 job.cancel() 270 // each assignment will immediately resume the collecting child coroutine, 271 // so no values will be skipped. 272 assertEquals(listOf(0, 1, 2, 3), values) 273} 274``` 275 276Be wary though of this approach: using `runCurrent`, `advanceTimeBy`, or `advanceUntilIdle` is, essentially, 277simulating some particular execution order, which is not guaranteed to happen in production code. 278For example, using `UnconfinedTestDispatcher` to fix this test reflects how, in production code, one could use 279`Dispatchers.Unconfined` to observe all emitted values without conflation, but the `runCurrent()` approach only 280states that the behavior would be observed if a dispatch were to happen at some chosen points. 281It is, therefore, recommended to structure tests in a way that does not rely on a particular interleaving, unless 282that is the intention. 283 284### The job hierarchy is completely different. 285 286- Structured concurrency is used, with the scope provided as the receiver of `runTest` actually being the scope of the 287 created coroutine. 288- Not `SupervisorJob` but a normal `Job` is used for the `TestCoroutineScope`. 289- The job passed as an argument is used as a parent job. 290 291Most tests should not be affected by this. In case your test is, try explicitly launching a child coroutine with a 292`SupervisorJob`; this should make the job hierarchy resemble what it used to be. 293 294```kotlin 295@Test 296fun testFoo() = runTest { 297 val deferred = async(SupervisorJob()) { 298 // test code 299 } 300 advanceUntilIdle() 301 deferred.getCompletionExceptionOrNull()?.let { 302 throw it 303 } 304} 305``` 306 307### Only a single call to `runTest` is permitted per test. 308 309In order to work on JS, only a single call to `runTest` must happen during one test, and its result must be returned 310immediately: 311 312```kotlin 313@Test 314fun testFoo(): TestResult { 315 // arbitrary code here 316 return runTest { 317 // ... 318 } 319} 320``` 321 322When used only on the JVM, `runTest` will work when called repeatedly, but this is not supported. 323Please only call `runTest` once per test, and if for some reason you can't, please tell us about in on the issue 324tracker. 325 326### It uses `TestScope`, not `TestCoroutineScope`, by default. 327 328There is a `runTestWithLegacyScope` method that allows migrating from `runBlockingTest` to `runTest` before migrating 329from `TestCoroutineScope` to `TestScope`, if exactly the `TestCoroutineScope` needs to be passed somewhere else and 330`TestScope` will not suffice. 331 332## Replace `TestCoroutineScope.cleanupTestCoroutines` with `runTest` 333 334Likely can be done together with the next step. 335 336Remove all calls to `TestCoroutineScope.cleanupTestCoroutines` from the code base. 337Instead, as the last step of each test, do `return scope.runTest`; if possible, the whole test body should go inside 338the `runTest` block. 339 340The cleanup procedure in `runTest` will not check that the virtual time doesn't advance during cleanup. 341If a test must check that no other delays are remaining after it has finished, the following form may help: 342```kotlin 343runTest { 344 testBody() 345 val timeAfterTest = currentTime() 346 advanceUntilIdle() // run the remaining tasks 347 assertEquals(timeAfterTest, currentTime()) // will fail if there were tasks scheduled at a later moment 348} 349``` 350Note that this will report time advancement even if the job scheduled at a later point was cancelled. 351 352It may be the case that `cleanupTestCoroutines` must be executed after de-initialization in `@AfterTest`, which happens 353outside the test itself. 354In this case, we propose that you write a wrapper of the form: 355 356```kotlin 357fun runTestAndCleanup(body: TestScope.() -> Unit) = runTest { 358 try { 359 body() 360 } finally { 361 // the usual cleanup procedures that used to happen before `cleanupTestCoroutines` 362 } 363} 364``` 365 366## Replace `runBlockingTest` with `runBlockingTestOnTestScope`, `createTestCoroutineScope` with `TestScope` 367 368Also, replace `runTestWithLegacyScope` with just `runTest`. 369All of this can be done in parallel with replacing `runBlockingTest` with `runTest`. 370 371This step should remove all uses of `TestCoroutineScope`, explicit or implicit. 372 373Replacing `runTestWithLegacyScope` and `runBlockingTest` with `runTest` and `runBlockingTestOnTestScope` should be 374straightforward if there is no more code left that requires passing exactly `TestCoroutineScope` to it. 375Some tests may fail because `TestCoroutineScope.cleanupTestCoroutines` and the cleanup procedure in `runTest` 376handle cancelled tasks differently: if there are *cancelled* jobs pending at the moment of 377`TestCoroutineScope.cleanupTestCoroutines`, they are ignored, whereas `runTest` will report them. 378 379Of all the methods supported by `TestCoroutineScope`, only `cleanupTestCoroutines` is not provided on `TestScope`, 380and its usages should have been removed during the previous step. 381 382## Replace `runBlocking` with `runTest` 383 384Now that `runTest` works properly with asynchronous completions, `runBlocking` is only occasionally useful. 385As is, most uses of `runBlocking` in tests come from the need to interact with dispatchers that execute on other 386threads, like `Dispatchers.IO` or `Dispatchers.Default`. 387 388## Replace `TestCoroutineDispatcher` with `UnconfinedTestDispatcher` and `StandardTestDispatcher` 389 390`TestCoroutineDispatcher` is a dispatcher with two modes: 391* ("unpaused") Almost (but not quite) unconfined, with the ability to eagerly enter `launch` and `async` blocks. 392* ("paused") Behaving like a `StandardTestDispatcher`. 393 394In one of the earlier steps, we replaced `pauseDispatcher` with `StandardTestDispatcher` usage, and replaced the 395implicit `TestCoroutineScope` dispatcher in `runBlockingTest` with `UnconfinedTestDispatcher` during migration to 396`runTest`. 397 398Now, the rest of the usages should be replaced with whichever dispatcher is most appropriate. 399 400## Simplify code by removing unneeded entities 401 402Likely, now some code has the form 403 404```kotlin 405val dispatcher = StandardTestDispatcher() 406val scope = TestScope(dispatcher) 407 408@BeforeTest 409fun setUp() { 410 Dispatchers.setMain(dispatcher) 411} 412 413@AfterTest 414fun tearDown() { 415 Dispatchers.resetMain() 416} 417 418@Test 419fun testFoo() = scope.runTest { 420 // ... 421} 422``` 423 424The point of this pattern is to ensure that the test runs with the same `TestCoroutineScheduler` as the one used for 425`Dispatchers.Main`. 426 427However, now this can be simplified to just 428 429```kotlin 430@BeforeTest 431fun setUp() { 432 Dispatchers.setMain(StandardTestDispatcher()) 433} 434 435@AfterTest 436fun tearDown() { 437 Dispatchers.resetMain() 438} 439 440@Test 441fun testFoo() = runTest { 442 // ... 443} 444``` 445 446The reason this works is that all entities that depend on `TestCoroutineScheduler` will attempt to acquire one from 447the current `Dispatchers.Main`. 448