@@ -7,6 +7,8 @@ package kotlinx.coroutines.test
7
7
import kotlinx.coroutines.*
8
8
import kotlinx.coroutines.channels.*
9
9
import kotlinx.coroutines.flow.*
10
+ import kotlinx.coroutines.test.internal.*
11
+ import kotlinx.coroutines.test.internal.TestMainDispatcher
10
12
import kotlin.coroutines.*
11
13
12
14
/* *
@@ -15,10 +17,32 @@ import kotlin.coroutines.*
15
17
* This dispatcher is similar to [Dispatchers.Unconfined]: the tasks that it executes are not confined to any particular
16
18
* thread and form an event loop; it's different in that it skips delays, as all [TestDispatcher]s do.
17
19
*
20
+ * Like [Dispatchers.Unconfined], this one does not provide guarantees about the execution order when several coroutines
21
+ * are queued in this dispatcher. However, we ensure that the [launch] and [async] blocks at the top level of [runTest]
22
+ * are entered eagerly. This allows launching child coroutines and not calling [runCurrent] for them to start executing.
23
+ *
24
+ * ```
25
+ * @Test
26
+ * fun testEagerlyEnteringChildCoroutines() = runTest(UnconfinedTestDispatcher()) {
27
+ * var entered = false
28
+ * val deferred = CompletableDeferred<Unit>()
29
+ * var completed = false
30
+ * launch {
31
+ * entered = true
32
+ * deferred.await()
33
+ * completed = true
34
+ * }
35
+ * assertTrue(entered) // `entered = true` already executed.
36
+ * assertFalse(completed) // however, the child coroutine then suspended, so it is enqueued.
37
+ * deferred.complete(Unit) // resume the coroutine.
38
+ * assertTrue(completed) // now the child coroutine is immediately completed.
39
+ * }
40
+ * ```
41
+ *
18
42
* Using this [TestDispatcher] can greatly simplify writing tests where it's not important which thread is used when and
19
43
* in which order the queued coroutines are executed.
20
- * The typical use case for this is launching child coroutines that are resumed immediately, without going through a
21
- * dispatch; this can be helpful for testing [Channel] and [StateFlow] usages.
44
+ * Another typical use case for this dispatcher is launching child coroutines that are resumed immediately, without
45
+ * going through a dispatch; this can be helpful for testing [Channel] and [StateFlow] usages.
22
46
*
23
47
* ```
24
48
* @Test
@@ -40,14 +64,16 @@ import kotlin.coroutines.*
40
64
* }
41
65
* ```
42
66
*
43
- * However, please be aware that, like [Dispatchers.Unconfined], this is a specific dispatcher with execution order
67
+ * Please be aware that, like [Dispatchers.Unconfined], this is a specific dispatcher with execution order
44
68
* guarantees that are unusual and not shared by most other dispatchers, so it can only be used reliably for testing
45
69
* functionality, not the specific order of actions.
46
70
* See [Dispatchers.Unconfined] for a discussion of the execution order guarantees.
47
71
*
48
72
* In order to support delay skipping, this dispatcher is linked to a [TestCoroutineScheduler], which is used to control
49
- * the virtual time and can be shared among many test dispatchers. If no [scheduler] is passed as an argument, a new one
50
- * is created.
73
+ * the virtual time and can be shared among many test dispatchers.
74
+ * If no [scheduler] is passed as an argument, [Dispatchers.Main] is checked, and if it was mocked with a
75
+ * [TestDispatcher] via [Dispatchers.setMain], the [TestDispatcher.scheduler] of the mock dispatcher is used; if
76
+ * [Dispatchers.Main] is not mocked with a [TestDispatcher], a new [TestCoroutineScheduler] is created.
51
77
*
52
78
* Additionally, [name] can be set to distinguish each dispatcher instance when debugging.
53
79
*
@@ -56,14 +82,14 @@ import kotlin.coroutines.*
56
82
@ExperimentalCoroutinesApi
57
83
@Suppress(" FunctionName" )
58
84
public fun UnconfinedTestDispatcher (
59
- scheduler : TestCoroutineScheduler = TestCoroutineScheduler () ,
85
+ scheduler : TestCoroutineScheduler ? = null ,
60
86
name : String? = null
61
- ): TestDispatcher = UnconfinedTestDispatcherImpl (scheduler, name)
87
+ ): TestDispatcher = UnconfinedTestDispatcherImpl (scheduler ? : mainTestScheduler ? : TestCoroutineScheduler () , name)
62
88
63
89
private class UnconfinedTestDispatcherImpl (
64
90
override val scheduler : TestCoroutineScheduler ,
65
91
private val name : String? = null
66
- ): TestDispatcher() {
92
+ ) : TestDispatcher() {
67
93
68
94
override fun isDispatchNeeded (context : CoroutineContext ): Boolean = false
69
95
@@ -103,22 +129,24 @@ private class UnconfinedTestDispatcherImpl(
103
129
* run these pending tasks, which will block until there are no more tasks scheduled at this point in time, or, when
104
130
* inside [runTest], call [yield] to yield the (only) thread used by [runTest] to the newly-launched coroutines.
105
131
*
106
- * If a [scheduler] is not passed as an argument, a new one is created.
132
+ * If no [scheduler] is passed as an argument, [Dispatchers.Main] is checked, and if it was mocked with a
133
+ * [TestDispatcher] via [Dispatchers.setMain], the [TestDispatcher.scheduler] of the mock dispatcher is used; if
134
+ * [Dispatchers.Main] is not mocked with a [TestDispatcher], a new [TestCoroutineScheduler] is created.
107
135
*
108
136
* One can additionally pass a [name] in order to more easily distinguish this dispatcher during debugging.
109
137
*
110
138
* @see UnconfinedTestDispatcher for a dispatcher that is not confined to any particular thread.
111
139
*/
112
140
@Suppress(" FunctionName" )
113
141
public fun StandardTestDispatcher (
114
- scheduler : TestCoroutineScheduler = TestCoroutineScheduler () ,
142
+ scheduler : TestCoroutineScheduler ? = null ,
115
143
name : String? = null
116
- ): TestDispatcher = StandardTestDispatcherImpl (scheduler, name)
144
+ ): TestDispatcher = StandardTestDispatcherImpl (scheduler ? : mainTestScheduler ? : TestCoroutineScheduler () , name)
117
145
118
146
private class StandardTestDispatcherImpl (
119
147
override val scheduler : TestCoroutineScheduler = TestCoroutineScheduler (),
120
148
private val name : String? = null
121
- ): TestDispatcher() {
149
+ ) : TestDispatcher() {
122
150
123
151
override fun dispatch (context : CoroutineContext , block : Runnable ) {
124
152
checkSchedulerInContext(scheduler, context)
@@ -127,3 +155,6 @@ private class StandardTestDispatcherImpl(
127
155
128
156
override fun toString (): String = " ${name ? : " StandardTestDispatcher" } [scheduler=$scheduler ]"
129
157
}
158
+
159
+ private val mainTestScheduler
160
+ get() = ((Dispatchers .Main as ? TestMainDispatcher )?.delegate as ? TestDispatcher )?.scheduler
0 commit comments