@@ -44,9 +44,9 @@ import kotlin.coroutines.CoroutineContext
44
44
*
45
45
* @throws UncompletedCoroutinesError If the [testBody] does not complete (or cancel) all coroutines that it launches
46
46
* (including coroutines suspended on await).
47
- * @throws UnhandledExceptionsError If an uncaught exception is not handled by [testBody]
47
+ * @throws Throwable If an uncaught exception was captured by this test it will be rethrown.
48
48
*
49
- * @param dispatcher An optional dispatcher, during [testBody] execution [TestCoroutineDispatcher .dispatchImmediately] will be set to false
49
+ * @param context An optional dispatcher, during [testBody] execution [DelayController .dispatchImmediately] will be set to false
50
50
* @param testBody The code of the unit-test.
51
51
*
52
52
* @see [runBlockingTest]
@@ -106,10 +106,15 @@ fun TestCoroutineScope.asyncTest(testBody: TestCoroutineScope.() -> Unit) =
106
106
*
107
107
* ```
108
108
*
109
- * [runBlockingTest] will allow tests to finish successfully while started coroutines are unfinished. In addition unhandled
110
- * exceptions inside coroutines will not fail the test.
109
+ * This method requires that all coroutines launched inside [testBody] complete, or are cancelled, as part of the test
110
+ * conditions.
111
+ *
112
+ * In unhandled exceptions inside coroutines will not fail the test.
111
113
*
112
- * @param dispatcher An optional dispatcher, during [testBody] execution [TestCoroutineDispatcher.dispatchImmediately] will be set to true
114
+ * @throws UncompletedCoroutinesError If the [testBody] does not complete (or cancel) all coroutines that it launches
115
+ * (including coroutines suspended on await).
116
+ *
117
+ * @param context An optional context, during [testBody] execution [DelayController.dispatchImmediately] will be set to true
113
118
* @param testBody The code of the unit-test.
114
119
*
115
120
* @see [asyncTest]
@@ -127,58 +132,44 @@ fun runBlockingTest(context: CoroutineContext? = null, testBody: suspend Corouti
127
132
scope.testBody()
128
133
scope.cleanupTestCoroutines()
129
134
}
135
+ val job = checkNotNull(safeContext[Job ]) { " Job required for asyncTest" }
136
+ val activeChildren = job.children.filter { it.isActive }.toList()
137
+ if (activeChildren.isNotEmpty()) {
138
+ throw UncompletedCoroutinesError (" Test finished with active jobs: ${activeChildren} " )
139
+ }
130
140
} finally {
131
141
dispatcher.dispatchImmediately = oldDispatch
132
142
}
133
143
}
134
144
135
145
/* *
136
- * Convenience method for calling runBlocking on an existing [TestCoroutineScope].
137
- *
138
- * [block] will be executed in immediate execution mode, similar to [runBlockingTest].
146
+ * Convenience method for calling [runBlockingTest] on an existing [TestCoroutineScope].
139
147
*/
140
- fun <T > TestCoroutineScope.runBlocking (block : suspend CoroutineScope .() -> T ): T {
141
- val oldDispatch = dispatchImmediately
142
- dispatchImmediately = true
143
- try {
144
- return runBlocking(coroutineContext, block)
145
- } finally {
146
- dispatchImmediately = oldDispatch
147
- }
148
+ fun TestCoroutineScope.runBlockingTest (block : suspend CoroutineScope .() -> Unit ) {
149
+ runBlockingTest(coroutineContext, block)
148
150
}
149
151
150
152
/* *
151
- * Convenience method for calling runBlocking on an existing [TestCoroutineDispatcher].
153
+ * Convenience method for calling [runBlockingTest] on an existing [TestCoroutineDispatcher].
152
154
*
153
- * [block] will be executed in immediate execution mode, similar to [runBlockingTest].
154
155
*/
155
- fun <T > TestCoroutineDispatcher.runBlocking (block : suspend CoroutineScope .() -> T ): T {
156
- val oldDispatch = dispatchImmediately
157
- dispatchImmediately = true
158
- try {
159
- return runBlocking(this , block)
160
- } finally {
161
- dispatchImmediately = oldDispatch
162
- }
156
+ fun TestCoroutineDispatcher.runBlockingTest (block : suspend CoroutineScope .() -> Unit ) {
157
+ runBlockingTest(this , block)
163
158
}
164
159
165
160
private fun CoroutineContext?.checkArguments (): Pair <CoroutineContext , ContinuationInterceptor > {
166
161
var safeContext= this ? : TestCoroutineExceptionHandler () + TestCoroutineDispatcher ()
167
162
168
163
val dispatcher = safeContext[ContinuationInterceptor ].run {
169
164
this ?.let {
170
- if (this !is DelayController ) {
171
- throw IllegalArgumentException (" Dispatcher must implement DelayController" )
172
- }
165
+ require(this is DelayController ) { " Dispatcher must implement DelayController" }
173
166
}
174
167
this ? : TestCoroutineDispatcher ()
175
168
}
176
169
177
170
val exceptionHandler = safeContext[CoroutineExceptionHandler ].run {
178
171
this ?.let {
179
- if (this !is ExceptionCaptor ) {
180
- throw IllegalArgumentException (" coroutineExceptionHandler must implement ExceptionCaptor" )
181
- }
172
+ require(this is ExceptionCaptor ) { " coroutineExceptionHandler must implement ExceptionCaptor" }
182
173
}
183
174
this ? : TestCoroutineExceptionHandler ()
184
175
}
0 commit comments