skipped when the test thread becomes idle. 02 Eliminate real concurrency because all code actually runs on one thread. 03 Precise control of virtual time allows testing complex concurrency scenarios. DNA.inc
1 CoroutineDispatcher (constructor) parameters Real dispatcher can be replaced with TestDispatcher. Leaks implementation details (“this runs on IO”). Future churn: if you add CPU- bound work, you’ll need to add another dispatcher param. Usage of default parameter makes it easy to forget replacing it in test environments
params needed. Implementation chooses the dispatcher it needs, when it needs it. Only affects Main, not IO/Default. Global mutable state; risky in parallel tests. Options for replacing real dispatchers Injecting Dispatchers — Option 2 setMain
replaced with TestDispatcher Cleaner constructor than many individual params. Implementation chooses the dispatcher it needs, when it needs it. Extra DI surface threaded everywhere Injecting Dispatchers — Option 3 DispatcherProvider
1 2 3 4 5 6 7 8 9 10 11 12 interface operator fun get operator fun interface val interface CoroutineContext { E Element (key Key E ) E (context CoroutineContext) CoroutineContext Element CoroutineContext { key Key } Key E Element } < : > : < > : ? : : : : <*> < : > plus
1 2 3 4 5 6 7 8 9 10 11 12 interface operator fun get operator fun interface val interface CoroutineContext { E Element (key Key E ) E (context CoroutineContext) CoroutineContext Element CoroutineContext { key Key } Key E Element } < : > : < > : ? : : : : <*> < : > plus
1 2 3 4 5 6 7 8 9 10 11 12 interface operator fun get operator fun interface val interface CoroutineContext { E Element (key Key E ) E (context CoroutineContext) CoroutineContext Element CoroutineContext { key Key } Key E Element } < : > : < > : ? : : : : <*> < : > plus
type Simplified Framework 1 2 3 4 5 6 7 8 9 10 11 12 interface operator fun get operator fun interface val interface CoroutineContext { E Element (key Key E ) E (context CoroutineContext) CoroutineContext Element CoroutineContext { key Key } Key E Element } < : > : < > : ? : : : : <*> < : > plus
class val override val companion object ( name String ) CoroutineContext. { key CoroutineContext.Key CoroutineName CoroutineContext.Key CoroutineName } CoroutineName Element : : : <*> = : < > DNA.inc How is the context defined? Example Context Element
• CoroutineContext is like an immutable Map of elements. All elements that make up a coroutine context are a coroutine context themselves. The key of every element defines the type (class or interface) of that element. CoroutineContexts can easily be combined together DNA.inc
for storing key-value pairs 02 Suspending methods can access the caller’s context through Continuation 03 CoroutineContext Elements are copied to child-coroutines DNA.inc
CoroutineContext Real dispatcher can be replaced with TestDispatcher No dispatcher params. Implementation chooses the dispatcher it needs, when it needs it. Does not feel bolted on. Some understanding of CoroutineContext inheritance.
• Classes that do not have a CoroutineScope don’t need any constructor injection. Classes that create a CoroutineScope need to constructor inject a CoroutineContext and tests pass the context of the TestScope. For classes that constructor inject a CoroutineScope the test should pass a TestScope, backgroundScope or a custom scope created with the TestScope’s Context DNA.inc