@@ -107,6 +107,8 @@ on Kotlin/JS. The main differences are the following:
107
107
108
108
* ** The calls to ` delay ` are automatically skipped** , preserving the relative execution order of the tasks. This way,
109
109
it's possible to make tests finish more-or-less immediately.
110
+ * ** The execution times out after 10 seconds** , cancelling the test coroutine to prevent tests from hanging forever
111
+ and eating up the CI resources.
110
112
* ** Controlling the virtual time** : in case just skipping delays is not sufficient, it's possible to more carefully
111
113
guide the execution, advancing the virtual time by a duration, draining the queue of the awaiting tasks, or running
112
114
the tasks scheduled at the present moment.
@@ -115,6 +117,31 @@ on Kotlin/JS. The main differences are the following:
115
117
Sometimes, especially when working with third-party code, it's impossible to mock all the dispatchers in use.
116
118
[ runTest] will handle the situations where some code runs in dispatchers not integrated with the test module.
117
119
120
+ ## Timeout
121
+
122
+ Test automatically time out after 10 seconds. For example, this test will fail with a timeout exception:
123
+
124
+ ``` kotlin
125
+ @Test
126
+ fun testHanging () = runTest {
127
+ CompletableDeferred <Unit >().await() // will hang forever
128
+ }
129
+ ```
130
+
131
+ In case the test is expected to take longer than 10 seconds, the timeout can be increased by passing the ` timeout `
132
+ parameter:
133
+
134
+ ``` kotlin
135
+ @Test
136
+ fun testTakingALongTime () = runTest(timeout = 30 .seconds) {
137
+ val result = withContext(Dispatchers .Default ) {
138
+ delay(20 .seconds) // this delay is not in the test dispatcher and will not be skipped
139
+ 3
140
+ }
141
+ assertEquals(3 , result)
142
+ }
143
+ ```
144
+
118
145
## Delay-skipping
119
146
120
147
To test regular suspend functions, which may have a delay, just run them inside the [ runTest] block.
@@ -163,30 +190,35 @@ fun testWithMultipleDelays() = runTest {
163
190
164
191
## Controlling the virtual time
165
192
166
- Inside [ runTest] , the following operations are supported:
193
+ Inside [ runTest] , the execution is scheduled by [ TestCoroutineScheduler] , which is a virtual time scheduler.
194
+ The scheduler has several special methods that allow controlling the virtual time:
167
195
* ` currentTime ` gets the current virtual time.
168
196
* ` runCurrent() ` runs the tasks that are scheduled at this point of virtual time.
169
197
* ` advanceUntilIdle() ` runs all enqueued tasks until there are no more.
170
198
* ` advanceTimeBy(timeDelta) ` runs the enqueued tasks until the current virtual time advances by ` timeDelta ` .
199
+ * ` timeSource ` returns a ` TimeSource ` that uses the virtual time.
171
200
172
201
``` kotlin
173
202
@Test
174
203
fun testFoo () = runTest {
175
204
launch {
176
- println (1 ) // executes during runCurrent()
177
- delay(1_000 ) // suspends until time is advanced by at least 1_000
178
- println (2 ) // executes during advanceTimeBy(2_000)
179
- delay(500 ) // suspends until the time is advanced by another 500 ms
180
- println (3 ) // also executes during advanceTimeBy(2_000)
181
- delay(5_000 ) // will suspend by another 4_500 ms
182
- println (4 ) // executes during advanceUntilIdle()
205
+ val workDuration = testScheduler.timeSource.measureTime {
206
+ println (1 ) // executes during runCurrent()
207
+ delay(1_000 ) // suspends until time is advanced by at least 1_000
208
+ println (2 ) // executes during advanceTimeBy(2_000)
209
+ delay(500 ) // suspends until the time is advanced by another 500 ms
210
+ println (3 ) // also executes during advanceTimeBy(2_000)
211
+ delay(5_000 ) // will suspend by another 4_500 ms
212
+ println (4 ) // executes during advanceUntilIdle()
213
+ }
214
+ assertEquals(6500 .milliseconds, workDuration) // the work took 6_500 ms of virtual time
183
215
}
184
216
// the child coroutine has not run yet
185
- runCurrent()
217
+ testScheduler. runCurrent()
186
218
// the child coroutine has called println(1), and is suspended on delay(1_000)
187
- advanceTimeBy(2_000 ) // progress time, this will cause two calls to `delay` to resume
219
+ testScheduler. advanceTimeBy(2 .seconds ) // progress time, this will cause two calls to `delay` to resume
188
220
// the child coroutine has called println(2) and println(3) and suspends for another 4_500 virtual milliseconds
189
- advanceUntilIdle() // will run the child coroutine to completion
221
+ testScheduler. advanceUntilIdle() // will run the child coroutine to completion
190
222
assertEquals(6500 , currentTime) // the child coroutine finished at virtual time of 6_500 milliseconds
191
223
}
192
224
```
@@ -265,6 +297,32 @@ fun testSubject() = scope.runTest {
265
297
}
266
298
```
267
299
300
+ ## Running background work
301
+
302
+ Sometimes, the fact that [ runTest] waits for all the coroutines to finish is undesired.
303
+ For example, the system under test may need to receive data from coroutines that always run in the background.
304
+ Emulating such coroutines by launching them from the test body is not sufficient, because [ runTest] will wait for them
305
+ to finish, which they never typically do.
306
+
307
+ For these cases, there is a special coroutine scope: [ TestScope.backgroundScope] .
308
+ Coroutines launched in it will be cancelled at the end of the test.
309
+
310
+ ``` kotlin
311
+ @Test
312
+ fun testExampleBackgroundJob () = runTest {
313
+ val channel = Channel <Int >()
314
+ backgroundScope.launch {
315
+ var i = 0
316
+ while (true ) {
317
+ channel.send(i++ )
318
+ }
319
+ }
320
+ repeat(100 ) {
321
+ assertEquals(it, channel.receive())
322
+ }
323
+ }
324
+ ```
325
+
268
326
## Eagerly entering ` launch ` and ` async ` blocks
269
327
270
328
Some tests only test functionality and don't particularly care about the precise order in which coroutines are
@@ -357,7 +415,7 @@ either dependency injection, a service locator, or a default parameter, if it is
357
415
358
416
### Status of the API
359
417
360
- This API is experimental and it is may change before migrating out of experimental (while it is marked as
418
+ Many parts of the API is experimental, and it is may change before migrating out of experimental (while it is marked as
361
419
[ ` @ExperimentalCoroutinesApi ` ] [ ExperimentalCoroutinesApi ] ).
362
420
Changes during experimental may have deprecation applied when possible, but it is not
363
421
advised to use the API in stable code before it leaves experimental due to possible breaking changes.
@@ -388,6 +446,7 @@ If you have any suggestions for improvements to this experimental API please sha
388
446
[ setMain ] : https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/set-main.html
389
447
[ TestScope.testScheduler ] : https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-test-scope/test-scheduler.html
390
448
[ TestScope.runTest ] : https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/run-test.html
449
+ [ TestScope.backgroundScope ] : https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-test-scope/background-scope.html
391
450
[ runCurrent ] : https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/run-current.html
392
451
393
452
<!-- - END -->
0 commit comments