Kotlin 协程:在测试 Android Presenter 时切换上下文

Boh*_*sen 3 android unit-testing kotlin kotlinx.coroutines

我最近开始在我的 Android 项目中使用 kotlin 协程,但我遇到了一些问题。许多人将其称为代码异味。

我正在使用 MVP 架构,其中协程在我的演示者中启动,如下所示:

// WorklistPresenter.kt
...
override fun loadWorklist() {
    ...
    launchAsync { mViewModel.getWorklist() }
    ...
Run Code Online (Sandbox Code Playgroud)

launchAsync函数以这种方式实现(在我的 WorklistPresenter 类扩展的 BasePresenter 类中):

@Synchronized
protected fun launchAsync(block: suspend CoroutineScope.() -> Unit): Job {
    return launch(UI) { block() }
}
Run Code Online (Sandbox Code Playgroud)

问题在于我使用的是依赖于 Android 框架的 UI 协程上下文。我无法将其更改为另一个协程上下文而不会遇到ViewRootImpl$CalledFromWrongThreadException. 为了能够对此进行单元测试,我创建了一个 BasePresenter 的副本,其中包含不同的实现launchAsync

protected fun launchAsync(block: suspend CoroutineScope.() -> Unit): Job {
    runBlocking { block() }
    return mock<Job>()
}
Run Code Online (Sandbox Code Playgroud)

对我来说这是一个问题,因为现在我的 BasePresenter 必须在两个地方维护。所以我的问题是。如何更改我的实现以支持简单的测试?

jha*_*ott 6

我最近了解了 Kotlin 协程,教我的人向我展示了解决这个问题的好方法。

您创建一个提供上下文的接口,具有默认实现:

interface CoroutineContextProvider {
    val main: CoroutineContext
        get() = Dispatchers.Main
    val io: CoroutineContext
        get() = Dispatchers.IO

    class Default : CoroutineContextProvider
}
Run Code Online (Sandbox Code Playgroud)

并且您将 this ( CoroutineContextProvider.Default()) 注入到演示者构造函数中,无论是手动还是使用注入框架。然后在你的代码中使用它提供的上下文:provider.main; provider.io; 或任何你想定义的。现在,您可以愉快地使用launch,并withContext使用从您的提供商对象这些情况下,知道它会在你的应用程序正常运行,但您可以在测试过程中提供不同的上下文。

从您的测试中注入此提供程序的不同实现,其中所有上下文都在 Dispatchers.Unconfined

class TestingCoroutineContextProvider : CoroutineContextProvider {
    @ExperimentalCoroutinesApi
    override val main: CoroutineContext
        get() = Dispatchers.Unconfined
    @ExperimentalCoroutinesApi
    override val io: CoroutineContext
        get() = Dispatchers.Unconfined
}
Run Code Online (Sandbox Code Playgroud)

当你模拟挂起函数时,调用它包裹着runBlocking,这将确保所有操作都发生在调用线程(你的测试)中。它在此处进行了解释(请参阅有关“无限制与受限制的调度程序”的部分)。