• Home
  • History
  • Annotate
Name Date Size #Lines LOC

..--

api/23-Nov-2023-6858

resources/META-INF/23-Nov-2023-129

src/23-Nov-2023-699344

test/23-Nov-2023-949771

README.mdD23-Nov-202320.2 KiB458358

build.gradle.ktsD23-Nov-202374 43

README.md

1# Module kotlinx-coroutines-test
2
3Test utilities for `kotlinx.coroutines`.
4
5This package provides testing utilities for effectively testing coroutines.
6
7## Using in your project
8
9Add `kotlinx-coroutines-test` to your project test dependencies:
10```
11dependencies {
12    testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.4.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
21test in situations where the platform `Main` dispatcher is not available, or you wish to replace `Dispatchers.Main` with a
22testing dispatcher.
23
24Once 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
28You can override the `Main` implementation using [setMain][setMain] method with any [CoroutineDispatcher] implementation, e.g.:
29
30```kotlin
31
32class 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```
55Calling `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
60To test regular suspend functions or coroutines started with `launch` or `async` use the [runBlockingTest] coroutine
61builder that provides extra test control to coroutines.
62
631. Auto-advancing of time for regular suspend functions
642. Explicit time control for testing multiple coroutines
653. Eager execution of `launch` or `async` code blocks
664. Pause, manually advance, and restart the execution of coroutines in a test
675. Report uncaught exceptions as test failures
68
69### Testing regular suspend functions
70
71To test regular suspend functions, which may have a delay, you can use the [runBlockingTest] builder to start a testing
72coroutine. Any calls to `delay` will automatically advance virtual time by the amount delayed.
73
74```kotlin
75@Test
76fun testFoo() = runBlockingTest { // a coroutine with an extra test control
77    val actual = foo()
78    // ...
79}
80
81suspend 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
91Inside of [runBlockingTest], both [launch] and [async] will start a new coroutine that may run concurrently with the
92test case.
93
94To make common testing situations easier, by default the body of the coroutine is executed *eagerly* until
95the first call to [delay] or [yield].
96
97```kotlin
98@Test
99fun testFooWithLaunch() = runBlockingTest {
100    foo()
101    // the coroutine launched by foo() is completed before foo() returns
102    // ...
103}
104
105fun 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
113suspend fun bar() {}
114```
115
116`runBlockingTest` will auto-progress virtual time until all coroutines are completed before returning. If any coroutines
117are 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
123If the coroutine created by `launch` or `async` calls `delay` then the [runBlockingTest] will not auto-progress time
124right away. This allows tests to observe the interaction of multiple coroutines with different delays.
125
126To 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
131fun 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
139suspend 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
149exiting. This is a convenience to avoid having to call [advanceUntilIdle][DelayController.advanceUntilIdle]
150as the last line of many common test cases.
151If any coroutines cannot complete by advancing time, an [UncompletedCoroutinesError] is thrown.
152
153### Testing `withTimeout` using `runBlockingTest`
154
155Time 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
158Depending on the code, causing the code to suspend may need to use different mocking or fake techniques. For this
159example an uncompleted `Deferred<Foo>` is provided to the function under test via parameter injection.
160
161```kotlin
162@Test(expected = TimeoutCancellationException::class)
163fun 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
170fun 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
181call to `withTimeout` is in a regular suspend function, consider calling `launch` or `async` inside your test body to
182create a second coroutine.
183
184### Using `pauseDispatcher` for explicit execution of `runBlockingTest`
185
186The eager execution of `launch` and `async` bodies makes many tests easier, but some tests need more fine grained
187control of coroutine execution.
188
189To disable eager execution, you can call [pauseDispatcher][DelayController.pauseDispatcher]
190to pause the [TestCoroutineDispatcher] that [runBlockingTest] uses.
191
192When the dispatcher is paused, all coroutines will be added to a queue instead running. In addition, time will never
193auto-progress due to `delay` on a paused dispatcher.
194
195```kotlin
196@Test
197fun 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
209fun 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
218Using `pauseDispatcher` gives tests explicit control over the progress of time as well as the ability to enqueue all
219coroutines. As a best practice consider adding two tests, one paused and one eager, to test coroutines that have
220non-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.
223This 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]
225without a lambda block and then explicitly resume the dispatcher with [resumeDispatcher][DelayController.resumeDispatcher].
226
227## Integrating tests with structured concurrency
228
229Code 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
231classes 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
245By providing [TestCoroutineScope] a test case is able to control execution of coroutines, as well as ensure that
246uncaught exceptions thrown by coroutines are converted into test failures.
247
248### Providing `TestCoroutineScope` from `runBlockingTest`
249
250In simple cases, tests can use the [TestCoroutineScope] created by [runBlockingTest] directly.
251
252```kotlin
253@Test
254fun testFoo() = runBlockingTest {
255    foo() // runBlockingTest passed in a TestCoroutineScope as this
256}
257
258fun CoroutineScope.foo() {
259    launch {  // CoroutineScope for launch is the TestCoroutineScope provided by runBlockingTest
260        // ...
261    }
262}
263```
264
265This style is preferred when the `CoroutineScope` is passed through an extension function style.
266
267### Providing an explicit `TestCoroutineScope`
268
269In many cases, the direct style is not preferred because [CoroutineScope] may need to be provided through another means
270such as dependency injection or service locators.
271
272Tests can declare a [TestCoroutineScope] explicitly in the class to support these use cases.
273
274Since [TestCoroutineScope] is stateful in order to keep track of executing coroutines and uncaught exceptions, it is
275important to ensure that [cleanupTestCoroutines][TestCoroutineScope.cleanupTestCoroutines] is called after every test case.
276
277```kotlin
278class TestClass {
279    private val testScope = TestCoroutineScope()
280    private lateinit var subject: Subject
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
300class 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
310test 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
315While providing a [TestCoroutineScope] is slightly preferred due to the improved uncaught exception handling, there are
316many situations where it is easier to provide a [TestCoroutineDispatcher]. For example [Dispatchers.setMain][setMain]
317does not accept a [TestCoroutineScope] and requires a [TestCoroutineDispatcher] to control coroutine execution in
318tests.
319
320The main difference between `TestCoroutineScope` and `TestCoroutineDispatcher` is how uncaught exceptions are handled.
321When 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
325A test can use a `TestCoroutineDispatcher` without declaring an explicit `TestCoroutineScope`. This is preferred
326when the class under test allows a test to provide a [CoroutineDispatcher] but does not allow the test to provide a
327[CoroutineScope].
328
329Since [TestCoroutineDispatcher] is stateful in order to keep track of executing coroutines, it is
330important to ensure that [cleanupTestCoroutines][DelayController.cleanupTestCoroutines] is called after every test case.
331
332```kotlin
333class 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
355fun 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
363to test failures. However, exposing a `CoroutineScope` to callers of a function may lead to complicated code, in which
364case this is the preferred pattern.
365
366### Using `TestCoroutineScope` and `TestCoroutineDispatcher` without `runBlockingTest`
367
368It is supported to use both [TestCoroutineScope] and [TestCoroutineDispatcher] without using the [runBlockingTest]
369builder. Tests may need to do this in situations such as introducing multiple dispatchers and library writers may do
370this to provide alternatives to `runBlockingTest`.
371
372```kotlin
373@Test
374fun 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
382fun 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
393Calls to `withContext(Dispatchers.IO)` or `withContext(Dispatchers.Default)` are common in coroutines based codebases.
394Both dispatchers are not designed to interact with `TestCoroutineDispatcher`.
395
396Tests should provide a `TestCoroutineDispatcher` to replace these dispatchers if the `withContext` calls `delay` in the
397function under test. For example, a test that calls `veryExpensiveOne` should provide a `TestCoroutineDispatcher` using
398either dependency injection, a service locator, or a default parameter.
399
400```kotlin
401suspend fun veryExpensiveOne() = withContext(Dispatchers.Default) {
402    delay(1_000)
403    1 // for very expensive values of 1
404}
405```
406
407In situations where the code inside the `withContext` is very simple, it is not as important to provide a test
408dispatcher. 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
410directly, there is no need to inject a `TestCoroutineDispatcher` into this function.
411
412```kotlin
413suspend fun veryExpensiveTwo() = withContext(Dispatchers.Default) {
414    2 // for very expensive values of 2
415}
416```
417
418Tests should provide a `TestCoroutineDispatcher` to code that calls `withContext` to provide time control for
419delays, or when execution control is needed to test complex logic.
420
421
422### Status of the API
423
424This API is experimental and it is may change before migrating out of experimental (while it is marked as
425[`@ExperimentalCoroutinesApi`][ExperimentalCoroutinesApi]).
426Changes during experimental may have deprecation applied when possible, but it is not
427advised to use the API in stable code before it leaves experimental due to possible breaking changes.
428
429If 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