我最近开始在我的 Android 项目中使用 kotlin 协程,但我在使用它时遇到了一些问题。许多人会称之为代码味道。
我使用的是 MVP 架构,其中协程在我的演示器中像这样启动:
// WorklistPresenter.kt
...
override fun loadWorklist() {
...
launchAsync { mViewModel.getWorklist() }
...
launchAsync
函数是这样实现的(在我的 WorklistPresenter 类扩展的 BasePresenter 类中):
@Synchronized
protected fun launchAsync(block: suspend CoroutineScope.() -> Unit): Job {
return launch(UI) { block() }
}
问题在于我使用的是依赖于 Android 框架的 UI 协程上下文。如果不遇到 ViewRootImpl$CalledFromWrongThreadException
,我无法将其更改为另一个协程上下文。为了能够对此进行单元测试,我使用 launchAsync
的不同实现创建了我的 BasePresenter 的副本:
protected fun launchAsync(block: suspend CoroutineScope.() -> Unit): Job {
runBlocking { block() }
return mock<Job>()
}
对我来说这是个问题,因为现在我的 BasePresenter 必须在两个地方维护。所以我的问题是。如何更改我的实现以支持轻松测试?
最佳答案
我最近了解了 Kotlin 协程,教我的人向我展示了解决这个问题的好方法。
您创建一个提供上下文的接口(interface),具有默认实现:
interface CoroutineContextProvider {
val main: CoroutineContext
get() = Dispatchers.Main
val io: CoroutineContext
get() = Dispatchers.IO
class Default : CoroutineContextProvider
}
然后您将此 (CoroutineContextProvider.Default()
) 手动或使用注入(inject)框架注入(inject)到您的 Presenter 构造函数中。然后在您的代码中使用它提供的上下文:provider.main
; provider.io
;或者你想定义的任何东西。现在您可以愉快地使用 launch
和 withContext
使用来自您的提供者对象的这些上下文,知道它会在您的应用程序中正常工作,但您可以在测试期间提供不同的上下文。
从您的测试中注入(inject)此提供程序的不同实现,其中所有上下文都是 Dispatchers.Unconfined
class TestingCoroutineContextProvider : CoroutineContextProvider {
@ExperimentalCoroutinesApi
override val main: CoroutineContext
get() = Dispatchers.Unconfined
@ExperimentalCoroutinesApi
override val io: CoroutineContext
get() = Dispatchers.Unconfined
}
当您模拟挂起函数时,用 runBlocking
包裹调用它,这将确保所有操作都发生在调用线程(您的测试)中。解释了here (请参阅有关“不受限与受限 Dispatcher”的部分)。
关于android - Kotlin 协程 : Switching context when testing an Android Presenter,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48205095/