aboutsummaryrefslogtreecommitdiffstats
path: root/kotlinx-coroutines-test/README.md
blob: aefd5a3f07d31d170efdf765abf99dfce400e577 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
# Module kotlinx-coroutines-test

Test utilities for `kotlinx.coroutines`.

This package provides testing utilities for effectively testing coroutines.

## Using in your project

Add `kotlinx-coroutines-test` to your project test dependencies:
```
dependencies {
    testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.3'
}
```

**Do not** depend on this project in your main sources, all utilities are intended and designed to be used only from tests.

## Dispatchers.Main Delegation

`Dispatchers.setMain` will override the `Main` dispatcher in test situations. This is helpful when you want to execute a
test in situations where the platform `Main` dispatcher is not available, or you wish to replace `Dispatchers.Main` with a
testing dispatcher.

Once you have this dependency in the runtime,
[`ServiceLoader`](https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html) mechanism will overwrite
[Dispatchers.Main] with a testable implementation.

You can override the `Main` implementation using [setMain][setMain] method with any [CoroutineDispatcher] implementation, e.g.:

```kotlin

class SomeTest {
    
    private val mainThreadSurrogate = newSingleThreadContext("UI thread")

    @Before
    fun setUp() {
        Dispatchers.setMain(mainThreadSurrogate)
    }

    @After
    fun tearDown() {
        Dispatchers.resetMain() // reset main dispatcher to the original Main dispatcher
        mainThreadSurrogate.close()
    }
    
    @Test
    fun testSomeUI() = runBlocking {
        launch(Dispatchers.Main) {  // Will be launched in the mainThreadSurrogate dispatcher
            // ...
        }
    }
}
```
Calling `setMain` or `resetMain` immediately changes the `Main` dispatcher globally. The testable version of 
`Dispatchers.Main` installed by the `ServiceLoader` will delegate to the dispatcher provided by `setMain`.

## runBlockingTest

To test regular suspend functions or coroutines started with `launch` or `async` use the [runBlockingTest] coroutine 
builder that provides extra test control to coroutines. 

1. Auto-advancing of time for regular suspend functions
2. Explicit time control for testing multiple coroutines
3. Eager execution of `launch` or `async` code blocks
4. Pause, manually advance, and restart the execution of coroutines in a test
5. Report uncaught exceptions as test failures

### Testing regular suspend functions

To test regular suspend functions, which may have a delay, you can use the [runBlockingTest] builder to start a testing 
coroutine. Any calls to `delay` will automatically advance virtual time by the amount delayed.

```kotlin
@Test
fun testFoo() = runBlockingTest { // a coroutine with an extra test control
    val actual = foo() 
    // ...
}

suspend fun foo() {
    delay(1_000) // auto-advances virtual time by 1_000ms due to runBlockingTest
    // ...
}
```

`runBlockingTest` returns `Unit` so it may be used in a single expression with common testing libraries.

### Testing `launch` or `async`

Inside of [runBlockingTest], both [launch] and [async] will start a new coroutine that may run concurrently with the 
test case. 

To make common testing situations easier, by default the body of the coroutine is executed *eagerly* until 
the first call to [delay] or [yield].

```kotlin
@Test
fun testFooWithLaunch() = runBlockingTest {
    foo()
    // the coroutine launched by foo() is completed before foo() returns
    // ...
}

fun CoroutineScope.foo() {
     // This coroutines `Job` is not shared with the test code
     launch {
         bar()      // executes eagerly when foo() is called due to runBlockingTest
         println(1) // executes eagerly when foo() is called due to runBlockingTest
     }
}

suspend fun bar() {}
```

`runBlockingTest` will auto-progress virtual time until all coroutines are completed before returning. If any coroutines
are not able to complete, an [UncompletedCoroutinesError] will be thrown.

*Note:* The default eager behavior of [runBlockingTest] will ignore [CoroutineStart] parameters.

### Testing `launch` or `async` with `delay`

If the coroutine created by `launch` or `async` calls `delay` then the [runBlockingTest] will not auto-progress time 
right away. This allows tests to observe the interaction of multiple coroutines with different delays.

To control time in the test you can use the [DelayController] interface. The block passed to 
[runBlockingTest] can call any method on the `DelayController` interface.

```kotlin
@Test
fun testFooWithLaunchAndDelay() = runBlockingTest {
    foo()
    // the coroutine launched by foo has not completed here, it is suspended waiting for delay(1_000)
    advanceTimeBy(1_000) // progress time, this will cause the delay to resume
    // the coroutine launched by foo has completed here
    // ...
}

suspend fun CoroutineScope.foo() {
    launch {
        println(1)   // executes eagerly when foo() is called due to runBlockingTest
        delay(1_000) // suspends until time is advanced by at least 1_000
        println(2)   // executes after advanceTimeBy(1_000)
    }
}
```

*Note:* `runBlockingTest` will always attempt to auto-progress time until all coroutines are completed just before 
exiting. This is a convenience to avoid having to call [advanceUntilIdle][DelayController.advanceUntilIdle] 
as the last line of many common test cases.
If any coroutines cannot complete by advancing time, an [UncompletedCoroutinesError] is thrown.

### Testing `withTimeout` using `runBlockingTest`

Time control can be used to test timeout code. To do so, ensure that the function under test is suspended inside a 
`withTimeout` block and advance time until the timeout is triggered.

Depending on the code, causing the code to suspend may need to use different mocking or fake techniques. For this 
example an uncompleted `Deferred<Foo>` is provided to the function under test via parameter injection.

```kotlin
@Test(expected = TimeoutCancellationException::class)
fun testFooWithTimeout() = runBlockingTest {
    val uncompleted = CompletableDeferred<Foo>() // this Deferred<Foo> will never complete
    foo(uncompleted)
    advanceTimeBy(1_000) // advance time, which will cause the timeout to throw an exception
    // ...
}

fun CoroutineScope.foo(resultDeferred: Deferred<Foo>) {
    launch {
        withTimeout(1_000) {
            resultDeferred.await() // await() will suspend forever waiting for uncompleted
            // ...
        }
    }
}
```

*Note:* Testing timeouts is simpler with a second coroutine that can be suspended (as in this example). If the 
call to `withTimeout` is in a regular suspend function, consider calling `launch` or `async` inside your test body to 
create a second coroutine.

### Using `pauseDispatcher` for explicit execution of `runBlockingTest`

The eager execution of `launch` and `async` bodies makes many tests easier, but some tests need more fine grained 
control of coroutine execution.

To disable eager execution, you can call [pauseDispatcher][DelayController.pauseDispatcher] 
to pause the [TestCoroutineDispatcher] that [runBlockingTest] uses.

When the dispatcher is paused, all coroutines will be added to a queue instead running. In addition, time will never 
auto-progress due to `delay` on a paused dispatcher.

```kotlin
@Test
fun testFooWithPauseDispatcher() = runBlockingTest {
    pauseDispatcher {
        foo()
        // the coroutine started by foo has not run yet
        runCurrent() // the coroutine started by foo advances to delay(1_000)
        // the coroutine started by foo has called println(1), and is suspended on delay(1_000)
        advanceTimeBy(1_000) // progress time, this will cause the delay to resume
        // the coroutine started by foo has called println(2) and has completed here
    }
    // ...
}

fun CoroutineScope.foo() {
    launch {
        println(1)   // executes after runCurrent() is called
        delay(1_000) // suspends until time is advanced by at least 1_000
        println(2)   // executes after advanceTimeBy(1_000)
    }
}
```

Using `pauseDispatcher` gives tests explicit control over the progress of time as well as the ability to enqueue all 
coroutines. As a best practice consider adding two tests, one paused and one eager, to test coroutines that have 
non-trivial external dependencies and side effects in their launch body.

*Important:* When passed a lambda block, `pauseDispatcher` will resume eager execution immediately after the block. 
This will cause time to auto-progress if there are any outstanding `delay` calls that were not resolved before the
`pauseDispatcher` block returned. In advanced situations tests can call [pauseDispatcher][DelayController.pauseDispatcher] 
without a lambda block and then explicitly resume the dispatcher with [resumeDispatcher][DelayController.resumeDispatcher].

## Integrating tests with structured concurrency

Code that uses structured concurrency needs a [CoroutineScope] in order to launch a coroutine. In order to integrate 
[runBlockingTest] with code that uses common structured concurrency patterns tests can provide one (or both) of these
classes to application code.  

 | Name | Description | 
 | ---- | ----------- | 
 | [TestCoroutineScope] | A [CoroutineScope] which provides detailed control over the execution of coroutines for tests and integrates with [runBlockingTest]. |
 | [TestCoroutineDispatcher] | A [CoroutineDispatcher] which can be used for tests and integrates with [runBlockingTest]. |
 
 Both classes are provided to allow for various testing needs. Depending on the code that's being 
 tested, it may be easier to provide a [TestCoroutineDispatcher]. For example [Dispatchers.setMain][setMain] will accept
 a [TestCoroutineDispatcher] but not a [TestCoroutineScope].
 
 [TestCoroutineScope] will always use a [TestCoroutineDispatcher] to execute coroutines. It 
 also uses [TestCoroutineExceptionHandler] to convert uncaught exceptions into test failures.

By providing [TestCoroutineScope] a test case is able to control execution of coroutines, as well as ensure that 
uncaught exceptions thrown by coroutines are converted into test failures.

### Providing `TestCoroutineScope` from `runBlockingTest`

In simple cases, tests can use the [TestCoroutineScope] created by [runBlockingTest] directly.

```kotlin
@Test
fun testFoo() = runBlockingTest {        
    foo() // runBlockingTest passed in a TestCoroutineScope as this
}

fun CoroutineScope.foo() {
    launch {  // CoroutineScope for launch is the TestCoroutineScope provided by runBlockingTest
        // ...
    }
}
```

This style is preferred when the `CoroutineScope` is passed through an extension function style.

### Providing an explicit `TestCoroutineScope`

In many cases, the direct style is not preferred because [CoroutineScope] may need to be provided through another means 
such as dependency injection or service locators.

Tests can declare a [TestCoroutineScope] explicitly in the class to support these use cases.

Since [TestCoroutineScope] is stateful in order to keep track of executing coroutines and uncaught exceptions, it is 
important to ensure that [cleanupTestCoroutines][TestCoroutineScope.cleanupTestCoroutines] is called after every test case. 

```kotlin
class TestClass {
    private val testScope = TestCoroutineScope()
    private lateinit var subject: Subject = null 
    
    @Before
    fun setup() {
        // provide the scope explicitly, in this example using a constructor parameter
        subject = Subject(testScope)
    }
    
    @After
    fun cleanUp() {
        testScope.cleanupTestCoroutines()
    }
    
    @Test
    fun testFoo() = testScope.runBlockingTest {
        // TestCoroutineScope.runBlockingTest uses the Dispatcher and exception handler provided by `testScope`
        subject.foo()
    }
}

class Subject(val scope: CoroutineScope) {
    fun foo() {
        scope.launch {
            // launch uses the testScope injected in setup
        }
    }
}
```

*Note:* [TestCoroutineScope], [TestCoroutineDispatcher], and [TestCoroutineExceptionHandler] are interfaces to enable 
test libraries to provide library specific integrations. For example, a JUnit4 `@Rule` may call 
[Dispatchers.setMain][setMain] then expose [TestCoroutineScope] for use in tests.

### Providing an explicit `TestCoroutineDispatcher`

While providing a [TestCoroutineScope] is slightly preferred due to the improved uncaught exception handling, there are 
many situations where it is easier to provide a [TestCoroutineDispatcher]. For example [Dispatchers.setMain][setMain] 
does not accept a [TestCoroutineScope] and requires a [TestCoroutineDispatcher] to control coroutine execution in 
tests.

The main difference between `TestCoroutineScope` and `TestCoroutineDispatcher` is how uncaught exceptions are handled. 
When using `TestCoroutineDispatcher` uncaught exceptions thrown in coroutines will use regular 
[coroutine exception handling](https://kotlinlang.org/docs/reference/coroutines/exception-handling.html). 
`TestCoroutineScope` will always use `TestCoroutineDispatcher` as it's dispatcher.

A test can use a `TestCoroutineDispatcher` without declaring an explicit `TestCoroutineScope`. This is preferred 
when the class under test allows a test to provide a [CoroutineDispatcher] but does not allow the test to provide a 
[CoroutineScope].

Since [TestCoroutineDispatcher] is stateful in order to keep track of executing coroutines, it is 
important to ensure that [cleanupTestCoroutines][DelayController.cleanupTestCoroutines] is called after every test case. 

```kotlin
class TestClass {
    private val testDispatcher = TestCoroutineDispatcher()
        
    @Before
    fun setup() {
        // provide the scope explicitly, in this example using a constructor parameter
        Dispatchers.setMain(testDispatcher)
    }
    
    @After
    fun cleanUp() {
        Dispatchers.resetMain()
        testDispatcher.cleanupTestCoroutines()
    }
    
    @Test
    fun testFoo() = testDispatcher.runBlockingTest {
        // TestCoroutineDispatcher.runBlockingTest uses `testDispatcher` to run coroutines 
        foo()
    }
}

fun foo() {
    MainScope().launch { 
        // launch will use the testDispatcher provided by setMain
    }
}
```

*Note:* Prefer to provide `TestCoroutineScope` when it does not complicate code since it will also elevate exceptions 
to test failures. However, exposing a `CoroutineScope` to callers of a function may lead to complicated code, in which 
case this is the preferred pattern.

### Using `TestCoroutineScope` and `TestCoroutineDispatcher` without `runBlockingTest`

It is supported to use both [TestCoroutineScope] and [TestCoroutineDispatcher] without using the [runBlockingTest] 
builder. Tests may need to do this in situations such as introducing multiple dispatchers and library writers may do 
this to provide alternatives to `runBlockingTest`.

```kotlin
@Test
fun testFooWithAutoProgress() {
    val scope = TestCoroutineScope()
    scope.foo()
    // foo is suspended waiting for time to progress
    scope.advanceUntilIdle()
    // foo's coroutine will be completed before here
}

fun CoroutineScope.foo() {
    launch {
        println(1)            // executes eagerly when foo() is called due to TestCoroutineScope
        delay(1_000)          // suspends until time is advanced by at least 1_000
        println(2)            // executes after advanceTimeUntilIdle
    }
} 
```

## Using time control with `withContext`

Calls to `withContext(Dispatchers.IO)` or `withContext(Dispatchers.Default)` are common in coroutines based codebases. 
Both dispatchers are not designed to interact with `TestCoroutineDispatcher`.

Tests should provide a `TestCoroutineDispatcher` to replace these dispatchers if the `withContext` calls `delay` in the
function under test. For example, a test that calls `veryExpensiveOne` should provide a `TestCoroutineDispatcher` using
either dependency injection, a service locator, or a default parameter. 

```kotlin
suspend fun veryExpensiveOne() = withContext(Dispatchers.Default) {
    delay(1_000)
    1 // for very expensive values of 1
}
```

In situations where the code inside the `withContext` is very simple, it is not as important to provide a test 
dispatcher. The function `veryExpensiveTwo` will behave identically in a `TestCoroutineDispatcher` and 
`Dispatchers.Default` after the thread switch for `Dispatchers.Default`. Because `withContext` always returns a value by
directly, there is no need to inject a `TestCoroutineDispatcher` into this function.

```kotlin
suspend fun veryExpensiveTwo() = withContext(Dispatchers.Default) {
    2 // for very expensive values of 2
}
```

Tests should provide a `TestCoroutineDispatcher` to code that calls `withContext` to provide time control for 
delays, or when execution control is needed to test complex logic.


### Status of the API

This API is experimental and it is may change before migrating out of experimental (while it is marked as
[`@ExperimentalCoroutinesApi`][ExperimentalCoroutinesApi]).
Changes during experimental may have deprecation applied when possible, but it is not
advised to use the API in stable code before it leaves experimental due to possible breaking changes.

If you have any suggestions for improvements to this experimental API please share them them on the 
[issue tracker](https://github.com/Kotlin/kotlinx.coroutines/issues).

<!--- MODULE kotlinx-coroutines-core -->
<!--- INDEX kotlinx.coroutines -->
[Dispatchers.Main]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-main.html
[CoroutineDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-dispatcher/index.html
[launch]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html
[async]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html
[delay]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/delay.html
[yield]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/yield.html
[CoroutineStart]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-start/index.html
[CoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
[ExperimentalCoroutinesApi]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-experimental-coroutines-api/index.html
<!--- MODULE kotlinx-coroutines-test -->
<!--- INDEX kotlinx.coroutines.test -->
[setMain]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/kotlinx.coroutines.-dispatchers/set-main.html
[runBlockingTest]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/run-blocking-test.html
[UncompletedCoroutinesError]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-uncompleted-coroutines-error/index.html
[DelayController]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-delay-controller/index.html
[DelayController.advanceUntilIdle]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-delay-controller/advance-until-idle.html
[DelayController.pauseDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-delay-controller/pause-dispatcher.html
[TestCoroutineDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-test-coroutine-dispatcher/index.html
[DelayController.resumeDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-delay-controller/resume-dispatcher.html
[TestCoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-test-coroutine-scope/index.html
[TestCoroutineExceptionHandler]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-test-coroutine-exception-handler/index.html
[TestCoroutineScope.cleanupTestCoroutines]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-test-coroutine-scope/cleanup-test-coroutines.html
[DelayController.cleanupTestCoroutines]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-delay-controller/cleanup-test-coroutines.html
<!--- END -->