Skip to content

Commit 31636fe

Browse files
committed
Use the TestCoroutineDispatcher from Dispatchers.Main by default
1 parent dd687c6 commit 31636fe

File tree

5 files changed

+44
-12
lines changed

5 files changed

+44
-12
lines changed

kotlinx-coroutines-test/common/src/TestBuilders.kt

+6-4
Original file line numberDiff line numberDiff line change
@@ -149,10 +149,12 @@ public expect class TestResult
149149
*
150150
* ### Task scheduling
151151
*
152-
* Delay-skipping is achieved by using virtual time. [TestCoroutineScheduler] is automatically created (if it wasn't
153-
* passed in some way in [context]) and can be used to control the virtual time, advancing it, running the tasks
154-
* scheduled at a specific time etc. Some convenience methods are available on [TestCoroutineScope] to control the
155-
* scheduler.
152+
* Delay-skipping is achieved by using virtual time.
153+
* If [Dispatchers.Main] is set to a [TestDispatcher] via [Dispatchers.setMain] before the test,
154+
* then its [TestCoroutineScheduler] is used;
155+
* otherwise, a new one is automatically created (or taken from [context] in some way) and can be used to control
156+
* the virtual time, advancing it, running the tasks scheduled at a specific time etc.
157+
* Some convenience methods are available on [TestCoroutineScope] to control the scheduler.
156158
*
157159
* Delays in code that runs inside dispatchers that don't use a [TestCoroutineScheduler] don't get skipped:
158160
* ```

kotlinx-coroutines-test/common/src/TestCoroutineScope.kt

+9-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ package kotlinx.coroutines.test
66

77
import kotlinx.coroutines.*
88
import kotlinx.coroutines.internal.*
9+
import kotlinx.coroutines.test.internal.*
10+
import kotlinx.coroutines.test.internal.TestMainDispatcher
911
import kotlin.coroutines.*
1012

1113
/**
@@ -135,7 +137,10 @@ public fun TestCoroutineScope(context: CoroutineContext = EmptyCoroutineContext)
135137
*
136138
* It ensures that all the test module machinery is properly initialized.
137139
* * If [context] doesn't define a [TestCoroutineScheduler] for orchestrating the virtual time used for delay-skipping,
138-
* a new one is created, unless a [TestDispatcher] is provided, in which case [TestDispatcher.scheduler] is used.
140+
* a new one is created, unless either
141+
* - a [TestDispatcher] is provided, in which case [TestDispatcher.scheduler] is used;
142+
* - at the moment of the creation of the scope, [Dispatchers.Main] is delegated to a [TestDispatcher], in which case
143+
* its [TestCoroutineScheduler] is used.
139144
* * If [context] doesn't have a [ContinuationInterceptor], a [StandardTestDispatcher] is created.
140145
* * A [CoroutineExceptionHandler] is created that makes [TestCoroutineScope.cleanupTestCoroutines] throw if there were
141146
* any uncaught exceptions, or forwards the exceptions further in a platform-specific manner if the cleanup was
@@ -168,7 +173,9 @@ public fun createTestCoroutineScope(context: CoroutineContext = EmptyCoroutineCo
168173
dispatcher
169174
}
170175
null -> {
171-
scheduler = context[TestCoroutineScheduler] ?: TestCoroutineScheduler()
176+
val mainDispatcherScheduler =
177+
((Dispatchers.Main as? TestMainDispatcher)?.delegate as? TestDispatcher)?.scheduler
178+
scheduler = context[TestCoroutineScheduler] ?: mainDispatcherScheduler ?: TestCoroutineScheduler()
172179
StandardTestDispatcher(scheduler)
173180
}
174181
else -> throw IllegalArgumentException("Dispatcher must implement TestDispatcher: $dispatcher")

kotlinx-coroutines-test/common/src/TestDispatchers.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import kotlin.jvm.*
1818
@ExperimentalCoroutinesApi
1919
public fun Dispatchers.setMain(dispatcher: CoroutineDispatcher) {
2020
require(dispatcher !is TestMainDispatcher) { "Dispatchers.setMain(Dispatchers.Main) is prohibited, probably Dispatchers.resetMain() should be used instead" }
21-
getTestMainDispatcher().setDispatcher(dispatcher)
21+
getTestMainDispatcher().delegate = dispatcher
2222
}
2323

2424
/**

kotlinx-coroutines-test/common/src/internal/TestMainDispatcher.kt

+1-5
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import kotlin.coroutines.*
1010
* The testable main dispatcher used by kotlinx-coroutines-test.
1111
* It is a [MainCoroutineDispatcher] that delegates all actions to a settable delegate.
1212
*/
13-
internal class TestMainDispatcher(private var delegate: CoroutineDispatcher):
13+
internal class TestMainDispatcher(var delegate: CoroutineDispatcher):
1414
MainCoroutineDispatcher(),
1515
Delay by (delegate as? Delay ?: defaultDelay)
1616
{
@@ -25,10 +25,6 @@ internal class TestMainDispatcher(private var delegate: CoroutineDispatcher):
2525

2626
override fun dispatchYield(context: CoroutineContext, block: Runnable) = delegate.dispatchYield(context, block)
2727

28-
fun setDispatcher(dispatcher: CoroutineDispatcher) {
29-
delegate = dispatcher
30-
}
31-
3228
fun resetDispatcher() {
3329
delegate = mainDispatcher
3430
}

kotlinx-coroutines-test/common/test/TestCoroutineScopeTest.kt

+27
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,33 @@ class TestCoroutineScopeTest {
4848
assertSame(scheduler, scope.coroutineContext[TestCoroutineScheduler])
4949
assertSame(dispatcher, scope.coroutineContext[ContinuationInterceptor])
5050
}
51+
// Reuses the scheduler of `Dispatchers.Main`
52+
run {
53+
val scheduler = TestCoroutineScheduler()
54+
val mainDispatcher = StandardTestDispatcher(scheduler)
55+
Dispatchers.setMain(mainDispatcher)
56+
try {
57+
val scope = createTestCoroutineScope()
58+
assertSame(scheduler, scope.coroutineContext[TestCoroutineScheduler])
59+
assertNotSame(mainDispatcher, scope.coroutineContext[ContinuationInterceptor])
60+
} finally {
61+
Dispatchers.resetMain()
62+
}
63+
}
64+
// Does not reuse the scheduler of `Dispatchers.Main` if one is explicitly passed
65+
run {
66+
val mainDispatcher = StandardTestDispatcher()
67+
Dispatchers.setMain(mainDispatcher)
68+
try {
69+
val scheduler = TestCoroutineScheduler()
70+
val scope = createTestCoroutineScope(scheduler)
71+
assertSame(scheduler, scope.coroutineContext[TestCoroutineScheduler])
72+
assertNotSame(mainDispatcher.scheduler, scope.coroutineContext[TestCoroutineScheduler])
73+
assertNotSame(mainDispatcher, scope.coroutineContext[ContinuationInterceptor])
74+
} finally {
75+
Dispatchers.resetMain()
76+
}
77+
}
5178
}
5279

5380
/** Tests that the cleanup procedure throws if there were uncompleted delays by the end. */

0 commit comments

Comments
 (0)