Name |
Date |
Size |
#Lines |
LOC |
||
---|---|---|---|---|---|---|
.. | - | - | ||||
resources/META-INF/ | 03-May-2024 | - | 12 | 9 | ||
src/ | 03-May-2024 | - | 691 | 342 | ||
test/ | 03-May-2024 | - | 949 | 771 | ||
README.md | D | 03-May-2024 | 20.2 KiB | 458 | 358 | |
build.gradle | D | 03-May-2024 | 73 | 4 | 3 |
README.md
1 # Module kotlinx-coroutines-test 2 3 Test utilities for `kotlinx.coroutines`. 4 5 This package provides testing utilities for effectively testing coroutines. 6 7 ## Using in your project 8 9 Add `kotlinx-coroutines-test` to your project test dependencies: 10 ``` 11 dependencies { 12 testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.1' 13 } 14 ``` 15 16 **Do not** depend on this project in your main sources, all utilities are intended and designed to be used only from tests. 17 18 ## Dispatchers.Main Delegation 19 20 `Dispatchers.setMain` will override the `Main` dispatcher in test situations. This is helpful when you want to execute a 21 test in situations where the platform `Main` dispatcher is not available, or you wish to replace `Dispatchers.Main` with a 22 testing dispatcher. 23 24 Once you have this dependency in the runtime, 25 [`ServiceLoader`](https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html) mechanism will overwrite 26 [Dispatchers.Main] with a testable implementation. 27 28 You can override the `Main` implementation using [setMain][setMain] method with any [CoroutineDispatcher] implementation, e.g.: 29 30 ```kotlin 31 32 class SomeTest { 33 34 private val mainThreadSurrogate = newSingleThreadContext("UI thread") 35 36 @Before 37 fun setUp() { 38 Dispatchers.setMain(mainThreadSurrogate) 39 } 40 41 @After 42 fun tearDown() { 43 Dispatchers.resetMain() // reset main dispatcher to the original Main dispatcher 44 mainThreadSurrogate.close() 45 } 46 47 @Test 48 fun testSomeUI() = runBlocking { 49 launch(Dispatchers.Main) { // Will be launched in the mainThreadSurrogate dispatcher 50 // ... 51 } 52 } 53 } 54 ``` 55 Calling `setMain` or `resetMain` immediately changes the `Main` dispatcher globally. The testable version of 56 `Dispatchers.Main` installed by the `ServiceLoader` will delegate to the dispatcher provided by `setMain`. 57 58 ## runBlockingTest 59 60 To test regular suspend functions or coroutines started with `launch` or `async` use the [runBlockingTest] coroutine 61 builder that provides extra test control to coroutines. 62 63 1. Auto-advancing of time for regular suspend functions 64 2. Explicit time control for testing multiple coroutines 65 3. Eager execution of `launch` or `async` code blocks 66 4. Pause, manually advance, and restart the execution of coroutines in a test 67 5. Report uncaught exceptions as test failures 68 69 ### Testing regular suspend functions 70 71 To test regular suspend functions, which may have a delay, you can use the [runBlockingTest] builder to start a testing 72 coroutine. Any calls to `delay` will automatically advance virtual time by the amount delayed. 73 74 ```kotlin 75 @Test 76 fun testFoo() = runBlockingTest { // a coroutine with an extra test control 77 val actual = foo() 78 // ... 79 } 80 81 suspend fun foo() { 82 delay(1_000) // auto-advances virtual time by 1_000ms due to runBlockingTest 83 // ... 84 } 85 ``` 86 87 `runBlockingTest` returns `Unit` so it may be used in a single expression with common testing libraries. 88 89 ### Testing `launch` or `async` 90 91 Inside of [runBlockingTest], both [launch] and [async] will start a new coroutine that may run concurrently with the 92 test case. 93 94 To make common testing situations easier, by default the body of the coroutine is executed *eagerly* until 95 the first call to [delay] or [yield]. 96 97 ```kotlin 98 @Test 99 fun testFooWithLaunch() = runBlockingTest { 100 foo() 101 // the coroutine launched by foo() is completed before foo() returns 102 // ... 103 } 104 105 fun CoroutineScope.foo() { 106 // This coroutines `Job` is not shared with the test code 107 launch { 108 bar() // executes eagerly when foo() is called due to runBlockingTest 109 println(1) // executes eagerly when foo() is called due to runBlockingTest 110 } 111 } 112 113 suspend fun bar() {} 114 ``` 115 116 `runBlockingTest` will auto-progress virtual time until all coroutines are completed before returning. If any coroutines 117 are not able to complete, an [UncompletedCoroutinesError] will be thrown. 118 119 *Note:* The default eager behavior of [runBlockingTest] will ignore [CoroutineStart] parameters. 120 121 ### Testing `launch` or `async` with `delay` 122 123 If the coroutine created by `launch` or `async` calls `delay` then the [runBlockingTest] will not auto-progress time 124 right away. This allows tests to observe the interaction of multiple coroutines with different delays. 125 126 To control time in the test you can use the [DelayController] interface. The block passed to 127 [runBlockingTest] can call any method on the `DelayController` interface. 128 129 ```kotlin 130 @Test 131 fun testFooWithLaunchAndDelay() = runBlockingTest { 132 foo() 133 // the coroutine launched by foo has not completed here, it is suspended waiting for delay(1_000) 134 advanceTimeBy(1_000) // progress time, this will cause the delay to resume 135 // the coroutine launched by foo has completed here 136 // ... 137 } 138 139 suspend fun CoroutineScope.foo() { 140 launch { 141 println(1) // executes eagerly when foo() is called due to runBlockingTest 142 delay(1_000) // suspends until time is advanced by at least 1_000 143 println(2) // executes after advanceTimeBy(1_000) 144 } 145 } 146 ``` 147 148 *Note:* `runBlockingTest` will always attempt to auto-progress time until all coroutines are completed just before 149 exiting. This is a convenience to avoid having to call [advanceUntilIdle][DelayController.advanceUntilIdle] 150 as the last line of many common test cases. 151 If any coroutines cannot complete by advancing time, an [UncompletedCoroutinesError] is thrown. 152 153 ### Testing `withTimeout` using `runBlockingTest` 154 155 Time control can be used to test timeout code. To do so, ensure that the function under test is suspended inside a 156 `withTimeout` block and advance time until the timeout is triggered. 157 158 Depending on the code, causing the code to suspend may need to use different mocking or fake techniques. For this 159 example an uncompleted `Deferred<Foo>` is provided to the function under test via parameter injection. 160 161 ```kotlin 162 @Test(expected = TimeoutCancellationException::class) 163 fun testFooWithTimeout() = runBlockingTest { 164 val uncompleted = CompletableDeferred<Foo>() // this Deferred<Foo> will never complete 165 foo(uncompleted) 166 advanceTimeBy(1_000) // advance time, which will cause the timeout to throw an exception 167 // ... 168 } 169 170 fun CoroutineScope.foo(resultDeferred: Deferred<Foo>) { 171 launch { 172 withTimeout(1_000) { 173 resultDeferred.await() // await() will suspend forever waiting for uncompleted 174 // ... 175 } 176 } 177 } 178 ``` 179 180 *Note:* Testing timeouts is simpler with a second coroutine that can be suspended (as in this example). If the 181 call to `withTimeout` is in a regular suspend function, consider calling `launch` or `async` inside your test body to 182 create a second coroutine. 183 184 ### Using `pauseDispatcher` for explicit execution of `runBlockingTest` 185 186 The eager execution of `launch` and `async` bodies makes many tests easier, but some tests need more fine grained 187 control of coroutine execution. 188 189 To disable eager execution, you can call [pauseDispatcher][DelayController.pauseDispatcher] 190 to pause the [TestCoroutineDispatcher] that [runBlockingTest] uses. 191 192 When the dispatcher is paused, all coroutines will be added to a queue instead running. In addition, time will never 193 auto-progress due to `delay` on a paused dispatcher. 194 195 ```kotlin 196 @Test 197 fun testFooWithPauseDispatcher() = runBlockingTest { 198 pauseDispatcher { 199 foo() 200 // the coroutine started by foo has not run yet 201 runCurrent() // the coroutine started by foo advances to delay(1_000) 202 // the coroutine started by foo has called println(1), and is suspended on delay(1_000) 203 advanceTimeBy(1_000) // progress time, this will cause the delay to resume 204 // the coroutine started by foo has called println(2) and has completed here 205 } 206 // ... 207 } 208 209 fun CoroutineScope.foo() { 210 launch { 211 println(1) // executes after runCurrent() is called 212 delay(1_000) // suspends until time is advanced by at least 1_000 213 println(2) // executes after advanceTimeBy(1_000) 214 } 215 } 216 ``` 217 218 Using `pauseDispatcher` gives tests explicit control over the progress of time as well as the ability to enqueue all 219 coroutines. As a best practice consider adding two tests, one paused and one eager, to test coroutines that have 220 non-trivial external dependencies and side effects in their launch body. 221 222 *Important:* When passed a lambda block, `pauseDispatcher` will resume eager execution immediately after the block. 223 This will cause time to auto-progress if there are any outstanding `delay` calls that were not resolved before the 224 `pauseDispatcher` block returned. In advanced situations tests can call [pauseDispatcher][DelayController.pauseDispatcher] 225 without a lambda block and then explicitly resume the dispatcher with [resumeDispatcher][DelayController.resumeDispatcher]. 226 227 ## Integrating tests with structured concurrency 228 229 Code that uses structured concurrency needs a [CoroutineScope] in order to launch a coroutine. In order to integrate 230 [runBlockingTest] with code that uses common structured concurrency patterns tests can provide one (or both) of these 231 classes to application code. 232 233 | Name | Description | 234 | ---- | ----------- | 235 | [TestCoroutineScope] | A [CoroutineScope] which provides detailed control over the execution of coroutines for tests and integrates with [runBlockingTest]. | 236 | [TestCoroutineDispatcher] | A [CoroutineDispatcher] which can be used for tests and integrates with [runBlockingTest]. | 237 238 Both classes are provided to allow for various testing needs. Depending on the code that's being 239 tested, it may be easier to provide a [TestCoroutineDispatcher]. For example [Dispatchers.setMain][setMain] will accept 240 a [TestCoroutineDispatcher] but not a [TestCoroutineScope]. 241 242 [TestCoroutineScope] will always use a [TestCoroutineDispatcher] to execute coroutines. It 243 also uses [TestCoroutineExceptionHandler] to convert uncaught exceptions into test failures. 244 245 By providing [TestCoroutineScope] a test case is able to control execution of coroutines, as well as ensure that 246 uncaught exceptions thrown by coroutines are converted into test failures. 247 248 ### Providing `TestCoroutineScope` from `runBlockingTest` 249 250 In simple cases, tests can use the [TestCoroutineScope] created by [runBlockingTest] directly. 251 252 ```kotlin 253 @Test 254 fun testFoo() = runBlockingTest { 255 foo() // runBlockingTest passed in a TestCoroutineScope as this 256 } 257 258 fun CoroutineScope.foo() { 259 launch { // CoroutineScope for launch is the TestCoroutineScope provided by runBlockingTest 260 // ... 261 } 262 } 263 ``` 264 265 This style is preferred when the `CoroutineScope` is passed through an extension function style. 266 267 ### Providing an explicit `TestCoroutineScope` 268 269 In many cases, the direct style is not preferred because [CoroutineScope] may need to be provided through another means 270 such as dependency injection or service locators. 271 272 Tests can declare a [TestCoroutineScope] explicitly in the class to support these use cases. 273 274 Since [TestCoroutineScope] is stateful in order to keep track of executing coroutines and uncaught exceptions, it is 275 important to ensure that [cleanupTestCoroutines][TestCoroutineScope.cleanupTestCoroutines] is called after every test case. 276 277 ```kotlin 278 class TestClass { 279 private val testScope = TestCoroutineScope() 280 private lateinit var subject: Subject = null 281 282 @Before 283 fun setup() { 284 // provide the scope explicitly, in this example using a constructor parameter 285 subject = Subject(testScope) 286 } 287 288 @After 289 fun cleanUp() { 290 testScope.cleanupTestCoroutines() 291 } 292 293 @Test 294 fun testFoo() = testScope.runBlockingTest { 295 // TestCoroutineScope.runBlockingTest uses the Dispatcher and exception handler provided by `testScope` 296 subject.foo() 297 } 298 } 299 300 class Subject(val scope: CoroutineScope) { 301 fun foo() { 302 scope.launch { 303 // launch uses the testScope injected in setup 304 } 305 } 306 } 307 ``` 308 309 *Note:* [TestCoroutineScope], [TestCoroutineDispatcher], and [TestCoroutineExceptionHandler] are interfaces to enable 310 test libraries to provide library specific integrations. For example, a JUnit4 `@Rule` may call 311 [Dispatchers.setMain][setMain] then expose [TestCoroutineScope] for use in tests. 312 313 ### Providing an explicit `TestCoroutineDispatcher` 314 315 While providing a [TestCoroutineScope] is slightly preferred due to the improved uncaught exception handling, there are 316 many situations where it is easier to provide a [TestCoroutineDispatcher]. For example [Dispatchers.setMain][setMain] 317 does not accept a [TestCoroutineScope] and requires a [TestCoroutineDispatcher] to control coroutine execution in 318 tests. 319 320 The main difference between `TestCoroutineScope` and `TestCoroutineDispatcher` is how uncaught exceptions are handled. 321 When using `TestCoroutineDispatcher` uncaught exceptions thrown in coroutines will use regular 322 [coroutine exception handling](https://kotlinlang.org/docs/reference/coroutines/exception-handling.html). 323 `TestCoroutineScope` will always use `TestCoroutineDispatcher` as it's dispatcher. 324 325 A test can use a `TestCoroutineDispatcher` without declaring an explicit `TestCoroutineScope`. This is preferred 326 when the class under test allows a test to provide a [CoroutineDispatcher] but does not allow the test to provide a 327 [CoroutineScope]. 328 329 Since [TestCoroutineDispatcher] is stateful in order to keep track of executing coroutines, it is 330 important to ensure that [cleanupTestCoroutines][DelayController.cleanupTestCoroutines] is called after every test case. 331 332 ```kotlin 333 class TestClass { 334 private val testDispatcher = TestCoroutineDispatcher() 335 336 @Before 337 fun setup() { 338 // provide the scope explicitly, in this example using a constructor parameter 339 Dispatchers.setMain(testDispatcher) 340 } 341 342 @After 343 fun cleanUp() { 344 Dispatchers.resetMain() 345 testDispatcher.cleanupTestCoroutines() 346 } 347 348 @Test 349 fun testFoo() = testDispatcher.runBlockingTest { 350 // TestCoroutineDispatcher.runBlockingTest uses `testDispatcher` to run coroutines 351 foo() 352 } 353 } 354 355 fun foo() { 356 MainScope().launch { 357 // launch will use the testDispatcher provided by setMain 358 } 359 } 360 ``` 361 362 *Note:* Prefer to provide `TestCoroutineScope` when it does not complicate code since it will also elevate exceptions 363 to test failures. However, exposing a `CoroutineScope` to callers of a function may lead to complicated code, in which 364 case this is the preferred pattern. 365 366 ### Using `TestCoroutineScope` and `TestCoroutineDispatcher` without `runBlockingTest` 367 368 It is supported to use both [TestCoroutineScope] and [TestCoroutineDispatcher] without using the [runBlockingTest] 369 builder. Tests may need to do this in situations such as introducing multiple dispatchers and library writers may do 370 this to provide alternatives to `runBlockingTest`. 371 372 ```kotlin 373 @Test 374 fun testFooWithAutoProgress() { 375 val scope = TestCoroutineScope() 376 scope.foo() 377 // foo is suspended waiting for time to progress 378 scope.advanceUntilIdle() 379 // foo's coroutine will be completed before here 380 } 381 382 fun CoroutineScope.foo() { 383 launch { 384 println(1) // executes eagerly when foo() is called due to TestCoroutineScope 385 delay(1_000) // suspends until time is advanced by at least 1_000 386 println(2) // executes after advanceTimeUntilIdle 387 } 388 } 389 ``` 390 391 ## Using time control with `withContext` 392 393 Calls to `withContext(Dispatchers.IO)` or `withContext(Dispatchers.Default)` are common in coroutines based codebases. 394 Both dispatchers are not designed to interact with `TestCoroutineDispatcher`. 395 396 Tests should provide a `TestCoroutineDispatcher` to replace these dispatchers if the `withContext` calls `delay` in the 397 function under test. For example, a test that calls `veryExpensiveOne` should provide a `TestCoroutineDispatcher` using 398 either dependency injection, a service locator, or a default parameter. 399 400 ```kotlin 401 suspend fun veryExpensiveOne() = withContext(Dispatchers.Default) { 402 delay(1_000) 403 1 // for very expensive values of 1 404 } 405 ``` 406 407 In situations where the code inside the `withContext` is very simple, it is not as important to provide a test 408 dispatcher. The function `veryExpensiveTwo` will behave identically in a `TestCoroutineDispatcher` and 409 `Dispatchers.Default` after the thread switch for `Dispatchers.Default`. Because `withContext` always returns a value by 410 directly, there is no need to inject a `TestCoroutineDispatcher` into this function. 411 412 ```kotlin 413 suspend fun veryExpensiveTwo() = withContext(Dispatchers.Default) { 414 2 // for very expensive values of 2 415 } 416 ``` 417 418 Tests should provide a `TestCoroutineDispatcher` to code that calls `withContext` to provide time control for 419 delays, or when execution control is needed to test complex logic. 420 421 422 ### Status of the API 423 424 This API is experimental and it is may change before migrating out of experimental (while it is marked as 425 [`@ExperimentalCoroutinesApi`][ExperimentalCoroutinesApi]). 426 Changes during experimental may have deprecation applied when possible, but it is not 427 advised to use the API in stable code before it leaves experimental due to possible breaking changes. 428 429 If you have any suggestions for improvements to this experimental API please share them them on the 430 [issue tracker](https://github.com/Kotlin/kotlinx.coroutines/issues). 431 432 <!--- MODULE kotlinx-coroutines-core --> 433 <!--- INDEX kotlinx.coroutines --> 434 [Dispatchers.Main]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-main.html 435 [CoroutineDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-dispatcher/index.html 436 [launch]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html 437 [async]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html 438 [delay]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/delay.html 439 [yield]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/yield.html 440 [CoroutineStart]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-start/index.html 441 [CoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html 442 [ExperimentalCoroutinesApi]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-experimental-coroutines-api/index.html 443 <!--- MODULE kotlinx-coroutines-test --> 444 <!--- INDEX kotlinx.coroutines.test --> 445 [setMain]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/kotlinx.coroutines.-dispatchers/set-main.html 446 [runBlockingTest]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/run-blocking-test.html 447 [UncompletedCoroutinesError]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-uncompleted-coroutines-error/index.html 448 [DelayController]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-delay-controller/index.html 449 [DelayController.advanceUntilIdle]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-delay-controller/advance-until-idle.html 450 [DelayController.pauseDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-delay-controller/pause-dispatcher.html 451 [TestCoroutineDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-test-coroutine-dispatcher/index.html 452 [DelayController.resumeDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-delay-controller/resume-dispatcher.html 453 [TestCoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-test-coroutine-scope/index.html 454 [TestCoroutineExceptionHandler]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-test-coroutine-exception-handler/index.html 455 [TestCoroutineScope.cleanupTestCoroutines]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-test-coroutine-scope/cleanup-test-coroutines.html 456 [DelayController.cleanupTestCoroutines]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-delay-controller/cleanup-test-coroutines.html 457 <!--- END --> 458