Inject Dispatcher
ID |
kotlin.inject_dispatcher |
Severity |
info |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Best Practice |
Language |
Kotlin |
Tags |
best-practice, concurrency |
Description
Reports direct references to the well-known kotlinx.coroutines.Dispatchers
singletons — Dispatchers.IO, Dispatchers.Default, Dispatchers.Main,
Dispatchers.Unconfined.
Hard-coding a dispatcher at a call site makes the surrounding code untestable:
tests cannot substitute a TestDispatcher or a synchronous deterministic
dispatcher without rewriting the producer.
Rationale
// Bad — Dispatchers.IO is baked into the call site
class Loader {
suspend fun load() = withContext(Dispatchers.IO) {
readFromDisk()
}
}
The function above cannot be unit-tested without actually scheduling work on
the I/O dispatcher. There is no seam through which a test can inject a
deterministic dispatcher and assert that withContext was used correctly.
Remediation
Take a CoroutineDispatcher as a constructor parameter (defaulting to the
production dispatcher) and pass that name to withContext and coroutine
builders.
class Loader(private val io: CoroutineDispatcher = Dispatchers.IO) {
suspend fun load() = withContext(io) {
readFromDisk()
}
}
In tests the constructor becomes the injection point:
val loader = Loader(io = StandardTestDispatcher())