@@ -87,6 +87,7 @@ public fun TestCoroutineDispatcher.runBlockingTest(block: suspend TestCoroutineS
87
87
* * Don't nest functions returning a [TestResult].
88
88
*/
89
89
@Suppress(" NO_ACTUAL_FOR_EXPECT" )
90
+ @DelicateCoroutinesApi
90
91
public expect class TestResult
91
92
92
93
/* *
@@ -161,44 +162,49 @@ public expect class TestResult
161
162
* @throws IllegalArgumentException if the [context] is invalid. See the [TestCoroutineScope] constructor docs for
162
163
* details.
163
164
*/
165
+ @DelicateCoroutinesApi
164
166
public fun runTest (
165
167
context : CoroutineContext = EmptyCoroutineContext ,
166
168
dispatchTimeoutMs : Long = DEFAULT_DISPATCH_TIMEOUT_MS ,
167
169
testBody : suspend TestCoroutineScope .() -> Unit
168
- ): TestResult = createTestResult {
169
- val testScope = TestCoroutineScope (context + RunningInRunTest ())
170
- val scheduler = testScope.testScheduler
171
- val deferred = testScope.async {
172
- testScope.testBody()
173
- }
174
- var completed = false
175
- while (! completed) {
176
- scheduler.advanceUntilIdle()
177
- if (deferred.isCompleted) {
178
- /* don't even enter `withTimeout`; this allows to use a timeout of zero to check that there are no
179
- non-trivial dispatches. */
180
- completed = true
181
- continue
170
+ ): TestResult {
171
+ if (context[RunningInRunTest ] != null )
172
+ throw IllegalStateException (" Calls to `runTest` can't be nested. Please read the docs on `TestResult` for details." )
173
+ return createTestResult {
174
+ val testScope = TestCoroutineScope (context + RunningInRunTest ())
175
+ val scheduler = testScope.testScheduler
176
+ val deferred = testScope.async {
177
+ testScope.testBody()
182
178
}
183
- try {
184
- withTimeout(dispatchTimeoutMs) {
185
- select<Unit > {
186
- deferred.onAwait {
187
- completed = true
188
- }
189
- scheduler.onDispatchEvent {
190
- // we received knowledge that `scheduler` observed a dispatch event, so we reset the timeout
179
+ var completed = false
180
+ while (! completed) {
181
+ scheduler.advanceUntilIdle()
182
+ if (deferred.isCompleted) {
183
+ /* don't even enter `withTimeout`; this allows to use a timeout of zero to check that there are no
184
+ non-trivial dispatches. */
185
+ completed = true
186
+ continue
187
+ }
188
+ try {
189
+ withTimeout(dispatchTimeoutMs) {
190
+ select<Unit > {
191
+ deferred.onAwait {
192
+ completed = true
193
+ }
194
+ scheduler.onDispatchEvent {
195
+ // we received knowledge that `scheduler` observed a dispatch event, so we reset the timeout
196
+ }
191
197
}
192
198
}
199
+ } catch (e: TimeoutCancellationException ) {
200
+ throw UncompletedCoroutinesError (" The test coroutine was not completed after waiting for $dispatchTimeoutMs ms" )
193
201
}
194
- } catch (e: TimeoutCancellationException ) {
195
- throw UncompletedCoroutinesError (" The test coroutine was not completed after waiting for $dispatchTimeoutMs ms" )
196
202
}
203
+ deferred.getCompletionExceptionOrNull()?.let {
204
+ throw it
205
+ }
206
+ testScope.cleanupTestCoroutines()
197
207
}
198
- deferred.getCompletionExceptionOrNull()?.let {
199
- throw it
200
- }
201
- testScope.cleanupTestCoroutines()
202
208
}
203
209
204
210
/* *
@@ -207,18 +213,33 @@ public fun runTest(
207
213
@Suppress(" NO_ACTUAL_FOR_EXPECT" ) // actually suppresses `TestResult`
208
214
internal expect fun createTestResult (testProcedure : suspend () -> Unit ): TestResult
209
215
210
- /* * TODO: docs */
216
+ /* *
217
+ * Runs a test in a [TestCoroutineScope] based on this one.
218
+ *
219
+ * Calls [runTest] using a coroutine context from this [TestCoroutineScope]. The [TestCoroutineScope] used to run
220
+ * [block] will be different from this one, but will reuse its [Job]; therefore, even if calling
221
+ * [TestCoroutineScope.cleanupTestCoroutines] on this scope were to complete its job, [runTest] won't complete it at the
222
+ * end of the test.
223
+ *
224
+ * Since this function returns [TestResult], in order to work correctly on the JS, its result must be returned
225
+ * immediately from the test body. See the docs for [TestResult] for details.
226
+ */
227
+ @DelicateCoroutinesApi
211
228
public fun TestCoroutineScope.runTest (
212
229
dispatchTimeoutMs : Long = DEFAULT_DISPATCH_TIMEOUT_MS ,
213
230
block : suspend TestCoroutineScope .() -> Unit
214
- ): TestResult {
215
- val ctx = this .coroutineContext
216
- if (ctx[RunningInRunTest ] != null )
217
- throw IllegalStateException (" Calls to `runTest` can't be nested. Please read the docs on `TestResult` for details." )
218
- return runTest(ctx, dispatchTimeoutMs, block)
219
- }
231
+ ): TestResult =
232
+ runTest(coroutineContext, dispatchTimeoutMs, block)
220
233
221
- /* * TODO: docs */
234
+ /* *
235
+ * Run a test using this [TestDispatcher].
236
+ *
237
+ * A convenience function that calls [runTest] with the given arguments.
238
+ *
239
+ * Since this function returns [TestResult], in order to work correctly on the JS, its result must be returned
240
+ * immediately from the test body. See the docs for [TestResult] for details.
241
+ */
242
+ @DelicateCoroutinesApi
222
243
public fun TestDispatcher.runTest (
223
244
dispatchTimeoutMs : Long = DEFAULT_DISPATCH_TIMEOUT_MS ,
224
245
block : suspend TestCoroutineScope .() -> Unit
@@ -230,4 +251,6 @@ private class RunningInRunTest: AbstractCoroutineContextElement(RunningInRunTest
230
251
companion object Key : CoroutineContext.Key<RunningInRunTest>
231
252
}
232
253
254
+ /* * The default timeout to use when waiting for asynchronous completions of the coroutines managed by
255
+ * a [TestCoroutineScheduler]. */
233
256
private const val DEFAULT_DISPATCH_TIMEOUT_MS = 10_000L
0 commit comments