Skip to content

Commit cb0ef71

Browse files
authored
Update the README of the test module to reflect the developments (#3645)
1 parent 2b865e2 commit cb0ef71

File tree

1 file changed

+71
-12
lines changed

1 file changed

+71
-12
lines changed

kotlinx-coroutines-test/README.md

Lines changed: 71 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ on Kotlin/JS. The main differences are the following:
107107

108108
* **The calls to `delay` are automatically skipped**, preserving the relative execution order of the tasks. This way,
109109
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.
110112
* **Controlling the virtual time**: in case just skipping delays is not sufficient, it's possible to more carefully
111113
guide the execution, advancing the virtual time by a duration, draining the queue of the awaiting tasks, or running
112114
the tasks scheduled at the present moment.
@@ -115,6 +117,31 @@ on Kotlin/JS. The main differences are the following:
115117
Sometimes, especially when working with third-party code, it's impossible to mock all the dispatchers in use.
116118
[runTest] will handle the situations where some code runs in dispatchers not integrated with the test module.
117119

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+
118145
## Delay-skipping
119146

120147
To test regular suspend functions, which may have a delay, just run them inside the [runTest] block.
@@ -163,30 +190,35 @@ fun testWithMultipleDelays() = runTest {
163190

164191
## Controlling the virtual time
165192

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:
167195
* `currentTime` gets the current virtual time.
168196
* `runCurrent()` runs the tasks that are scheduled at this point of virtual time.
169197
* `advanceUntilIdle()` runs all enqueued tasks until there are no more.
170198
* `advanceTimeBy(timeDelta)` runs the enqueued tasks until the current virtual time advances by `timeDelta`.
199+
* `timeSource` returns a `TimeSource` that uses the virtual time.
171200

172201
```kotlin
173202
@Test
174203
fun testFoo() = runTest {
175204
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
183215
}
184216
// the child coroutine has not run yet
185-
runCurrent()
217+
testScheduler.runCurrent()
186218
// 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
188220
// 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
190222
assertEquals(6500, currentTime) // the child coroutine finished at virtual time of 6_500 milliseconds
191223
}
192224
```
@@ -265,6 +297,32 @@ fun testSubject() = scope.runTest {
265297
}
266298
```
267299

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+
268326
## Eagerly entering `launch` and `async` blocks
269327

270328
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
357415

358416
### Status of the API
359417

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
361419
[`@ExperimentalCoroutinesApi`][ExperimentalCoroutinesApi]).
362420
Changes during experimental may have deprecation applied when possible, but it is not
363421
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
388446
[setMain]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/set-main.html
389447
[TestScope.testScheduler]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-test-scope/test-scheduler.html
390448
[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
391450
[runCurrent]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/run-current.html
392451

393452
<!--- END -->

0 commit comments

Comments
 (0)