Kotlin 协程单元测试失败,并显示“带有主调度程序的模块初始化失败”

tak*_*rsh 14 unit-testing kotlin kotlin-coroutines

为使用withContext(Dispatchers.Main)测试方法的kotlin 挂起方法运行单元测试时失败,出现以下异常:

我的协程库版本是kotlinx-coroutines-core:1.1.1kotlinx-coroutines-android:1.1.1

例子:

suspend fun methodToTest() {
        withContext(Dispatchers.Main) {
           doSomethingOnMainThread()
                val data = withContext(Dispatchers.IO) {
                    doSomethingOnIOThread()
                }
        }
    }
Run Code Online (Sandbox Code Playgroud)

此外,当我删除withContext(Dispatchers.Main)它时,它工作正常。

java.lang.IllegalStateException: Module with the Main dispatcher had failed to initialize. For tests Dispatchers.setMain from kotlinx-coroutines-test module can be used

at kotlinx.coroutines.internal.MissingMainCoroutineDispatcher.missing(MainDispatchers.kt:79)
at kotlinx.coroutines.internal.MissingMainCoroutineDispatcher.isDispatchNeeded(MainDispatchers.kt:54)
at kotlinx.coroutines.DispatchedKt.resumeCancellable(Dispatched.kt:373)
at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:25)
at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:152)
at kotlinx.coroutines.BuildersKt.withContext(Unknown Source)
Run Code Online (Sandbox Code Playgroud)

Emm*_*ali 19

在运行测试时,例如针对启动协程的 ViewModel,您最有可能陷入以下异常

java.lang.IllegalStateException:带有主调度程序的模块未能初始化。对于测试,可以使用 kotlinx-coroutines-test 模块中的 Dispatchers.setMain

这背后的原因是缺乏Looper.getMainLooper()真实应用程序中存在的测试环境。要解决此问题,您需要将 Main 调度程序与TestCoroutineDispatcher

确保你的 Gradle 文件有 coroutine-test 依赖

"org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutine_version"

解决方案 1 - 不可扩展

在您的测试类上定义以下内容 -> 用 @ExperimentalCoroutinesApi

val dispatcher = TestCoroutineDispatcher()

@Before
fun setup() {
    Dispatchers.setMain(dispatcher)
}

@After
fun tearDown() {
    Dispatchers.resetMain()
}
Run Code Online (Sandbox Code Playgroud)

注意:如果你有一个,你也可以Dispatchers.Main作为你的存储库的构造函数依赖项传递CoroutineDispatcher。建议不要在存储库/视图模型等上对您的调度程序进行硬编码WATCH-THIS PLEASEEEEEEEEE

为什么不可扩展:您需要在每个测试类上复制和粘贴相同的代码

解决方案 2 - 可扩展 [使用这个 -它被谷歌使用]

在此解决方案中,您将创建自定义规则。在您的测试包上添加一个实用程序类

@ExperimentalCoroutinesApi
class MainCoroutineRule(
    private val dispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()
) : TestWatcher(), TestCoroutineScope by TestCoroutineScope(dispatcher) {
    override fun starting(description: Description?) {
        super.starting(description)
        Dispatchers.setMain(dispatcher)
    }

    override fun finished(description: Description?) {
        super.finished(description)
        cleanupTestCoroutines()
        Dispatchers.resetMain()
    }
}
Run Code Online (Sandbox Code Playgroud)

如果您想了解上述实用程序类的说明,请参阅此CODE-LAB

在您的测试课程中,只需添加以下几行,您就可以开始了

@get:Rule
val coroutineRule = MainCoroutineRule()
Run Code Online (Sandbox Code Playgroud)

如果你有很多测试类,我想你会明白为什么这是可扩展的

解决方案 3 [我希望你不要到达这里]

您也可以使用Dispatchers.Unconfined 链接

A coroutine dispatcher that is not confined to any specific thread. It executes the initial continuation of a coroutine in the current call-frame and lets the coroutine resume in whatever thread that is used by the corresponding suspending function, without mandating any specific threading policy. Nested coroutines launched in this dispatcher form an event-loop to avoid stack overflows
Run Code Online (Sandbox Code Playgroud)

您可以按如下方式添加它

@Before
fun setup() {
    Dispatchers.setMain(Dispatchers.Unconfined)
}

@After
fun tearDown() {
    Dispatchers.resetMain()
}
Run Code Online (Sandbox Code Playgroud)

快乐编码。. . .

  • 完全遵循示例。我仍然遇到同样的错误。知道为什么吗? (3认同)

小智 11

您无权在单元测试中访问 Dispatchers.Main

https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/

Dispatchers.Main Delegation 部分详细说明了您需要做什么。

  • 它需要使用 Dispatchers.setMain(mainThreadSurrogate) 模拟主线程。还需要添加 kotlinx-coroutines-test 作为依赖项 (2认同)

jep*_*bio 6

就我而言,我已经为单元测试设置了主协程调度程序,但仍然不时看到一些错误。

我已添加到 build.gradle 中如下所示:

android {
  // ...
  testOptions { 
    unitTests.returnDefaultValues = true
  }
}
Run Code Online (Sandbox Code Playgroud)

我再也看不到那个错误了。


Vah*_*iri 5

现在您可以将其添加到您的测试中:

Dispatchers.setMain(Dispatchers.Unconfined)
Run Code Online (Sandbox Code Playgroud)

或其他调度员..它是实验性的,但它有效


And*_*lix 5

这是因为 Dispatcher.Main 丢失而引发的。它基于 Android,因此不能用于单元测试。解决方案位于协程团队的文档中。下面是解决我的问题并包含在文档中的示例。

class SomeTest {
    
    private val mainThreadSurrogate = newSingleThreadContext("UI thread")

    @Before
    fun setUp() {
        Dispatchers.setMain(mainThreadSurrogate)
    }

    @After
    fun tearDown() {
        Dispatchers.resetMain() // reset main dispatcher to the original Main dispatcher
        mainThreadSurrogate.close()
    }
    
    @Test
    fun testSomeUI() = runBlocking {
        launch(Dispatchers.Main) {  // Will be launched in the mainThreadSurrogate dispatcher
            // ...
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

您应该注意的是newSingleThreadContext("UI Thread")Dispatchers.setMain(mainThreadSurrogate)在任何测试之前都会调用它来创建主调度程序。