• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 @file:Suppress("DEPRECATION", "DEPRECATION_ERROR")
2 @file:JvmName("TestBuildersKt")
3 @file:JvmMultifileClass
4 
5 package kotlinx.coroutines.test
6 
7 import kotlinx.coroutines.*
8 import kotlin.coroutines.*
9 import kotlin.jvm.*
10 import kotlin.time.Duration.Companion.milliseconds
11 
12 /**
13  * Executes a [testBody] inside an immediate execution dispatcher.
14  *
15  * This method is deprecated in favor of [runTest]. Please see the
16  * [migration guide](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-test/MIGRATION.md)
17  * for an instruction on how to update the code for the new API.
18  *
19  * This is similar to [runBlocking] but it will immediately progress past delays and into [launch] and [async] blocks.
20  * You can use this to write tests that execute in the presence of calls to [delay] without causing your test to take
21  * extra time.
22  *
23  * ```
24  * @Test
25  * fun exampleTest() = runBlockingTest {
26  *     val deferred = async {
27  *         delay(1_000)
28  *         async {
29  *             delay(1_000)
30  *         }.await()
31  *     }
32  *
33  *     deferred.await() // result available immediately
34  * }
35  *
36  * ```
37  *
38  * This method requires that all coroutines launched inside [testBody] complete, or are cancelled, as part of the test
39  * conditions.
40  *
41  * Unhandled exceptions thrown by coroutines in the test will be re-thrown at the end of the test.
42  *
43  * @throws AssertionError If the [testBody] does not complete (or cancel) all coroutines that it launches
44  * (including coroutines suspended on join/await).
45  *
46  * @param context additional context elements. If [context] contains [CoroutineDispatcher] or [CoroutineExceptionHandler],
47  *        then they must implement [DelayController] and [TestCoroutineExceptionHandler] respectively.
48  * @param testBody The code of the unit-test.
49  */
50 @Deprecated(
51     "Use `runTest` instead to support completing from other dispatchers. " +
52         "Please see the migration guide for details: " +
53         "https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-test/MIGRATION.md",
54     level = DeprecationLevel.ERROR
55 )
56 // Since 1.6.0, kept as warning in 1.7.0, ERROR in 1.9.0 and removed as experimental later
runBlockingTestnull57 public fun runBlockingTest(
58     context: CoroutineContext = EmptyCoroutineContext,
59     testBody: suspend TestCoroutineScope.() -> Unit
60 ) {
61     val scope = createTestCoroutineScope(TestCoroutineDispatcher() + SupervisorJob() + context)
62     val scheduler = scope.testScheduler
63     val deferred = scope.async {
64         scope.testBody()
65     }
66     scheduler.advanceUntilIdle()
67     deferred.getCompletionExceptionOrNull()?.let {
68         throw it
69     }
70     scope.cleanupTestCoroutines()
71 }
72 
73 /**
74  * A version of [runBlockingTest] that works with [TestScope].
75  */
76 @Deprecated("Use `runTest` instead to support completing from other dispatchers.", level = DeprecationLevel.ERROR)
77 // Since 1.6.0, kept as warning in 1.7.0, ERROR in 1.9.0 and removed as experimental later
runBlockingTestOnTestScopenull78 public fun runBlockingTestOnTestScope(
79     context: CoroutineContext = EmptyCoroutineContext,
80     testBody: suspend TestScope.() -> Unit
81 ) {
82     val completeContext = TestCoroutineDispatcher() + SupervisorJob() + context
83     val startJobs = completeContext.activeJobs()
84     val scope = TestScope(completeContext).asSpecificImplementation()
85     scope.enter()
86     scope.start(CoroutineStart.UNDISPATCHED, scope) {
87         scope.testBody()
88     }
89     scope.testScheduler.advanceUntilIdle()
90     val throwable = try {
91         scope.getCompletionExceptionOrNull()
92     } catch (e: IllegalStateException) {
93         null // the deferred was not completed yet; `scope.legacyLeave()` should complain then about unfinished jobs
94     }
95     scope.backgroundScope.cancel()
96     scope.testScheduler.advanceUntilIdleOr { false }
97     throwable?.let {
98         val exceptions = try {
99             scope.legacyLeave()
100         } catch (e: UncompletedCoroutinesError) {
101             listOf()
102         }
103         throwAll(it, exceptions)
104         return
105     }
106     throwAll(null, scope.legacyLeave())
107     val jobs = completeContext.activeJobs() - startJobs
108     if (jobs.isNotEmpty())
109         throw UncompletedCoroutinesError("Some jobs were not completed at the end of the test: $jobs")
110 }
111 
112 /**
113  * Convenience method for calling [runBlockingTest] on an existing [TestCoroutineScope].
114  *
115  * This method is deprecated in favor of [runTest], whereas [TestCoroutineScope] is deprecated in favor of [TestScope].
116  * Please see the
117  * [migration guide](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-test/MIGRATION.md)
118  * for an instruction on how to update the code for the new API.
119  */
120 @Deprecated(
121     "Use `runTest` instead to support completing from other dispatchers. " +
122         "Please see the migration guide for details: " +
123         "https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-test/MIGRATION.md",
124     level = DeprecationLevel.ERROR
125 )
126 // Since 1.6.0, kept as warning in 1.7.0, ERROR in 1.9.0 and removed as experimental later
runBlockingTestnull127 public fun TestCoroutineScope.runBlockingTest(block: suspend TestCoroutineScope.() -> Unit): Unit =
128     runBlockingTest(coroutineContext, block)
129 
130 /**
131  * Convenience method for calling [runBlockingTestOnTestScope] on an existing [TestScope].
132  */
133 @Deprecated("Use `runTest` instead to support completing from other dispatchers.", level = DeprecationLevel.ERROR)
134 // Since 1.6.0, kept as warning in 1.7.0, ERROR in 1.9.0 and removed as experimental later
135 public fun TestScope.runBlockingTest(block: suspend TestScope.() -> Unit): Unit =
136     runBlockingTestOnTestScope(coroutineContext, block)
137 
138 /**
139  * Convenience method for calling [runBlockingTest] on an existing [TestCoroutineDispatcher].
140  *
141  * This method is deprecated in favor of [runTest], whereas [TestCoroutineScope] is deprecated in favor of [TestScope].
142  * Please see the
143  * [migration guide](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-test/MIGRATION.md)
144  * for an instruction on how to update the code for the new API.
145  */
146 @Deprecated(
147     "Use `runTest` instead to support completing from other dispatchers. " +
148         "Please see the migration guide for details: " +
149         "https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-test/MIGRATION.md",
150     level = DeprecationLevel.ERROR
151 )
152 // Since 1.6.0, kept as warning in 1.7.0, ERROR in 1.9.0 and removed as experimental later
153 public fun TestCoroutineDispatcher.runBlockingTest(block: suspend TestCoroutineScope.() -> Unit): Unit =
154     runBlockingTest(this, block)
155 
156 /**
157  * This is an overload of [runTest] that works with [TestCoroutineScope].
158  */
159 @ExperimentalCoroutinesApi
160 @Deprecated("Use `runTest` instead.", level = DeprecationLevel.ERROR)
161 // Since 1.6.0, kept as warning in 1.7.0, ERROR in 1.9.0 and removed as experimental later
162 public fun runTestWithLegacyScope(
163     context: CoroutineContext = EmptyCoroutineContext,
164     dispatchTimeoutMs: Long = DEFAULT_DISPATCH_TIMEOUT_MS,
165     testBody: suspend TestCoroutineScope.() -> Unit
166 ) {
167     if (context[RunningInRunTest] != null)
168         throw IllegalStateException("Calls to `runTest` can't be nested. Please read the docs on `TestResult` for details.")
169     val testScope = TestBodyCoroutine(createTestCoroutineScope(context + RunningInRunTest))
170     return createTestResult {
171         runTestCoroutineLegacy(
172             testScope,
173             dispatchTimeoutMs.milliseconds,
174             TestBodyCoroutine::tryGetCompletionCause,
175             testBody
176         ) {
177             try {
178                 testScope.cleanup()
179                 emptyList()
180             } catch (e: UncompletedCoroutinesError) {
181                 throw e
182             } catch (e: Throwable) {
183                 listOf(e)
184             }
185         }
186     }
187 }
188 
189 /**
190  * Runs a test in a [TestCoroutineScope] based on this one.
191  *
192  * Calls [runTest] using a coroutine context from this [TestCoroutineScope]. The [TestCoroutineScope] used to run the
193  * [block] will be different from this one, but will use its [Job] as a parent.
194  *
195  * Since this function returns [TestResult], in order to work correctly on the JS, its result must be returned
196  * immediately from the test body. See the docs for [TestResult] for details.
197  */
198 @ExperimentalCoroutinesApi
199 @Deprecated("Use `TestScope.runTest` instead.", level = DeprecationLevel.ERROR)
200 // Since 1.6.0, kept as warning in 1.7.0, ERROR in 1.9.0 and removed as experimental later
runTestnull201 public fun TestCoroutineScope.runTest(
202     dispatchTimeoutMs: Long = DEFAULT_DISPATCH_TIMEOUT_MS,
203     block: suspend TestCoroutineScope.() -> Unit
204 ): TestResult = runTestWithLegacyScope(coroutineContext, dispatchTimeoutMs, block)
205 
206 private class TestBodyCoroutine(
207     private val testScope: TestCoroutineScope,
208 ) : AbstractCoroutine<Unit>(testScope.coroutineContext, initParentJob = true, active = true), TestCoroutineScope {
209 
210     override val testScheduler get() = testScope.testScheduler
211 
212     @Deprecated(
213         "This deprecation is to prevent accidentally calling `cleanupTestCoroutines` in our own code.",
214         ReplaceWith("this.cleanup()"),
215         DeprecationLevel.ERROR
216     )
217     override fun cleanupTestCoroutines() =
218         throw UnsupportedOperationException(
219             "Calling `cleanupTestCoroutines` inside `runTest` is prohibited: " +
220                 "it will be called at the end of the test in any case."
221         )
222 
223     fun cleanup() = testScope.cleanupTestCoroutines()
224 
225     /** Throws an exception if the coroutine is not completing. */
226     fun tryGetCompletionCause(): Throwable? = completionCause
227 }
228