@@ -16,6 +16,7 @@ dependencies {
16
16
** Do not** depend on this project in your main sources, all utilities are intended and designed to be used only from tests.
17
17
18
18
## Dispatchers.Main Delegation
19
+
19
20
` Dispatchers.setMain ` will override the ` Main ` dispatcher in test situations. This is helpful when you want to execute a
20
21
test in situations where the platform ` Main ` dispatcher is not available, or you wish to replace ` Dispatchers.Main ` with a
21
22
testing dispatcher.
@@ -46,7 +47,7 @@ class SomeTest {
46
47
@Test
47
48
fun testSomeUI () = runBlocking {
48
49
launch(Dispatchers .Main ) { // Will be launched in the mainThreadSurrogate dispatcher
49
- .. .
50
+ // ...
50
51
}
51
52
}
52
53
}
@@ -66,19 +67,20 @@ builder that provides extra test control to coroutines.
66
67
5 . Report uncaught exceptions as test failures
67
68
68
69
### Testing regular suspend functions
70
+
69
71
To test regular suspend functions, which may have a delay, you can use the [ runBlockingTest] builder to start a testing
70
72
coroutine. Any calls to ` delay ` will automatically advance time.
71
73
72
74
``` kotlin
73
75
@Test
74
76
fun testFoo () = runBlockingTest { // a coroutine with a extra test control
75
77
val actual = foo()
76
- .. .
78
+ // ...
77
79
}
78
80
79
81
suspend fun foo () {
80
82
delay(1_000 ) // auto-advances without delay due to runBlockingTest
81
- .. .
83
+ // ...
82
84
}
83
85
```
84
86
@@ -94,10 +96,10 @@ the first [delay].
94
96
95
97
``` kotlin
96
98
@Test
97
- fun testFoo_withLaunch () = runBlockingTest {
99
+ fun testFooWithLaunch () = runBlockingTest {
98
100
foo()
99
101
// the coroutine launched by foo() is completed before foo() returns
100
- .. .
102
+ // ...
101
103
}
102
104
103
105
fun CoroutineScope.foo () {
@@ -124,12 +126,12 @@ To control time in the test you can use the [DelayController] interface. The blo
124
126
125
127
``` kotlin
126
128
@Test
127
- fun testFoo_withLaunch_andDelay () = runBlockingTest {
129
+ fun testFooWithLaunchAndDelay () = runBlockingTest {
128
130
foo()
129
131
// the coroutine launched by foo has not completed here, it is suspended waiting for delay(1_000)
130
132
advanceTimeBy(1_000 ) // progress time, this will cause the delay to resume
131
133
// foo() coroutine launched by foo has completed here
132
- .. .
134
+ // ...
133
135
}
134
136
135
137
suspend fun CoroutineScope.foo () {
@@ -142,7 +144,8 @@ suspend fun CoroutineScope.foo() {
142
144
```
143
145
144
146
* Note:* ` runBlockingTest ` will always attempt to auto-progress time until all coroutines are completed just before
145
- exiting. This is a convenience to avoid having to call [ advanceUntilIdle] as the last line of many common test cases.
147
+ exiting. This is a convenience to avoid having to call [ advanceUntilIdle] [ DelayController.advanceUntilIdle ]
148
+ as the last line of many common test cases.
146
149
If any coroutines cannot complete by advancing time, a [ UncompletedCoroutinesError] is thrown.
147
150
148
151
### Testing ` withTimeout ` using ` runBlockingTest `
@@ -155,18 +158,18 @@ example an uncompleted `Deferred<Foo>` is provided to the function under test vi
155
158
156
159
``` kotlin
157
160
@Test(expected = TimeoutCancellationException ::class )
158
- fun testFoo_withTimeout () {
161
+ fun testFooWithTimeout () {
159
162
val uncompleted = CompletableDeferred <Foo >() // this Deferred<Foo> will never complete
160
163
foo(uncompleted)
161
164
advanceTimeBy(1_000 ) // advance time, which will cause the timeout to throw an exception
162
- .. .
165
+ // ...
163
166
}
164
167
165
168
fun CoroutineScope.foo (resultDeferred : Deferred <Foo >) {
166
169
launch {
167
170
withTimeout(1_000 ) {
168
171
resultDeferred.await() // await() will suspend forever waiting for uncompleted
169
- .. .
172
+ // ...
170
173
}
171
174
}
172
175
}
@@ -181,15 +184,15 @@ create a second coroutine.
181
184
The eager execution of ` launch ` and ` async ` bodies makes many tests easier, but some tests need more fine grained
182
185
control of coroutine execution.
183
186
184
- To disable eager execution, you can call [ pauseDispatcher] to pause the [ TestCoroutineDispatcher ] that [ runBlockingTest ]
185
- uses.
187
+ To disable eager execution, you can call [ pauseDispatcher] [ DelayController.pauseDispatcher ]
188
+ to pause the [ TestCoroutineDispatcher ] that [ runBlockingTest ] uses.
186
189
187
190
When the dispatcher is paused, all coroutines will be added to a queue instead running. In addition, time will never
188
191
auto-progress due to ` delay ` on a paused dispatcher.
189
192
190
193
``` kotlin
191
194
@Test
192
- fun testFoo_withPauseDispatcher () = runBlockingTest {
195
+ fun testFooWithPauseDispatcher () = runBlockingTest {
193
196
pauseDispatcher {
194
197
foo()
195
198
// the coroutine started by foo has not run yet
@@ -198,7 +201,7 @@ fun testFoo_withPauseDispatcher() = runBlockingTest {
198
201
advanceTimeBy(1_000 ) // progress time, this will cause the delay to resume
199
202
// the coroutine started by foo has called println(2) and has completed here
200
203
}
201
- .. .
204
+ // ...
202
205
}
203
206
204
207
fun CoroutineScope.foo () {
@@ -216,8 +219,8 @@ non-trivial external dependencies and side effects in their launch body.
216
219
217
220
* Important:* When passed a lambda block, ` pauseDispatcher ` will resume eager execution immediately after the block.
218
221
This will cause time to auto-progress if there are any outstanding ` delay ` calls that were not resolved before the
219
- ` pauseDispatcher ` block returned. In advanced situations tests can call [ pauseDispatcher] [ pausedispatcher.noarg ] without a lambda block and
220
- then explicitly resume the dispatcher with [ resumeDispatcher] .
222
+ ` pauseDispatcher ` block returned. In advanced situations tests can call [ pauseDispatcher] [ DelayController.pauseDispatcher ]
223
+ without a lambda block and then explicitly resume the dispatcher with [ resumeDispatcher ] [ DelayController. resumeDispatcher] .
221
224
222
225
## Integrating tests with structured concurrency
223
226
@@ -241,6 +244,7 @@ By providing [TestCoroutineScope] a test case is able to control execution of co
241
244
uncaught exceptions thrown by coroutines are converted into test failures.
242
245
243
246
### Providing ` TestCoroutineScope ` from ` runBlockingTest `
247
+
244
248
In simple cases, tests can use the [ TestCoroutineScope] created by [ runBlockingTest] directly.
245
249
246
250
``` kotlin
@@ -251,21 +255,22 @@ fun testFoo() = runBlockingTest {
251
255
252
256
fun CoroutineScope.foo () {
253
257
launch { // CoroutineScope for launch is the TestCoroutineScope provided by runBlockingTest
254
- .. .
258
+ // ...
255
259
}
256
260
}
257
261
```
258
262
259
263
This style is preferred when the ` CoroutineScope ` is passed through an extension function style.
260
264
261
265
### Providing an explicit ` TestCoroutineScope `
266
+
262
267
In many cases, the direct style is not preferred because [ CoroutineScope] may need to be provided through anther means
263
268
such as dependency injection or service locators.
264
269
265
270
Tests can declare a [ TestCoroutineScope] explicitly in the class to support these use cases.
266
271
267
272
Since [ TestCoroutineScope] is stateful in order to keep track of executing coroutines and uncaught exceptions, it is
268
- important to ensure that [ cleanupTestCoroutines] [ cleanupTestCoroutines.scope ] is called after every test case.
273
+ important to ensure that [ cleanupTestCoroutines] [ TestCoroutineScope.cleanupTestCoroutines ] is called after every test case.
269
274
270
275
``` kotlin
271
276
class TestClass {
@@ -304,6 +309,7 @@ test libraries to provide library specific integrations. For example, a JUnit4 `
304
309
[ Dispatchers.setMain] [ setMain ] then expose [ TestCoroutineScope] for use in tests.
305
310
306
311
### Providing an explicit ` TestCoroutineDispatcher `
312
+
307
313
While providing a [ TestCoroutineScope] is slightly preferred due to the improved uncaught exception handling, there are
308
314
many situations where it is easier to provide a [ TestCoroutineDispatcher] . For example [ Dispatchers.setMain] [ setMain ]
309
315
does not accept a [ TestCoroutineScope] and requires a [ TestCoroutineDispatcher] to control coroutine execution in
@@ -319,7 +325,7 @@ when the class under test allows a test to provide a [CoroutineDispatcher] but d
319
325
[ CoroutineScope] .
320
326
321
327
Since [ TestCoroutineDispatcher] is stateful in order to keep track of executing coroutines, it is
322
- important to ensure that [ cleanupTestCoroutines] [ cleanupTestCoroutines.dispatcher ] is called after every test case.
328
+ important to ensure that [ cleanupTestCoroutines] [ TestCoroutineDispatcher.cleanupTestCoroutines ] is called after every test case.
323
329
324
330
``` kotlin
325
331
class TestClass {
@@ -363,7 +369,7 @@ this to provide alternatives to `runBlockingTest`.
363
369
364
370
``` kotlin
365
371
@Test
366
- fun testFoo_withAutoProgress () {
372
+ fun testFooWithAutoProgress () {
367
373
val scope = TestCoroutineScope ()
368
374
scope.foo()
369
375
// foo is suspended waiting for time to progress
@@ -381,6 +387,7 @@ fun CoroutineScope.foo() {
381
387
```
382
388
383
389
## Using time control with ` withContext `
390
+
384
391
Calls to ` withContext(Dispatchers.IO) ` or ` withContext(Dispatchers.Default) ` are common in coroutines based codebases.
385
392
Both dispatchers are not designed to interact with ` TestCoroutineDispatcher ` .
386
393
@@ -413,37 +420,34 @@ delays, or when execution control is needed to test complex logic.
413
420
### Status of the API
414
421
415
422
This API is experimental and it is may change before migrating out of experimental (while it is marked as
416
- ` @ExperimentalCoroutinesApi ` ). Changes during experimental may have deprecation applied when possible, but it is not
417
- advised to use the API before it leaves experimental due to possible breaking changes.
423
+ [ ` @ExperimentalCoroutinesApi ` ] [ ExperimentalCoroutinesApi ] ).
424
+ Changes during experimental may have deprecation applied when possible, but it is not
425
+ advised to use the API in stable code before it leaves experimental due to possible breaking changes.
418
426
419
427
If you have any suggestions for improvements to this experimental API please share them them on the
420
428
[ issue tracker] ( https://github.com/Kotlin/kotlinx.coroutines/issues ) .
421
429
422
-
423
430
<!-- - MODULE kotlinx-coroutines-core -->
424
431
<!-- - INDEX kotlinx.coroutines -->
425
432
[ Dispatchers.Main ] : https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-main.html
426
433
[ CoroutineDispatcher ] : https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-dispatcher/index.html
427
- [ CoroutineScope ] : https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/
428
434
[ launch ] : https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html
429
435
[ async ] : https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html
430
436
[ delay ] : https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/delay.html
437
+ [ CoroutineScope ] : https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
438
+ [ ExperimentalCoroutinesApi ] : https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-experimental-coroutines-api/index.html
431
439
<!-- - MODULE kotlinx-coroutines-test -->
432
440
<!-- - INDEX kotlinx.coroutines.test -->
433
441
[ setMain ] : https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/kotlinx.coroutines.-dispatchers/set-main.html
434
-
435
- [ runBlockingTest ] : https://notapage/
436
- [ UncompletedCoroutinesError ] : https://notapage/
437
- [ DelayController ] : https://notapage/
438
- [ pauseDispatcher ] : https://notapage/
439
- [ pauseDispatcher.noarg ] : https://notapage/noarg
440
- [ resumeDispatcher ] : https://notapage/
441
- [ advanceUntilIdle ] : https://notapage/
442
- [ advanceTimeBy ] : https://notapage/
443
- [ resumeDispatcher ] : https://notapage/
444
- [ TestCoroutineDispatcher ] : https://notapage/
445
- [ TestCoroutineExceptionHandler ] : https://notapage/
446
- [ TestCoroutineScope ] : https://notapage/
447
- [ cleanupTestCoroutines.scope ] : https://notapage/cleanupscope
448
- [ cleanupTestCoroutines.dispatcher ] : https://notapage/cleanupdispatcher
442
+ [ runBlockingTest ] : https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/run-blocking-test.html
443
+ [ UncompletedCoroutinesError ] : https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-uncompleted-coroutines-error/index.html
444
+ [ DelayController ] : https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-delay-controller/index.html
445
+ [ DelayController.advanceUntilIdle ] : https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-delay-controller/advance-until-idle.html
446
+ [ DelayController.pauseDispatcher ] : https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-delay-controller/pause-dispatcher.html
447
+ [ TestCoroutineDispatcher ] : https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-test-coroutine-dispatcher/index.html
448
+ [ DelayController.resumeDispatcher ] : https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-delay-controller/resume-dispatcher.html
449
+ [ TestCoroutineScope ] : https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-test-coroutine-scope/index.html
450
+ [ TestCoroutineExceptionHandler ] : https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-test-coroutine-exception-handler/index.html
451
+ [ TestCoroutineScope.cleanupTestCoroutines ] : https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-test-coroutine-scope/cleanup-test-coroutines.html
452
+ [ TestCoroutineDispatcher.cleanupTestCoroutines ] : https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-test-coroutine-dispatcher/cleanup-test-coroutines.html
449
453
<!-- - END -->
0 commit comments