【问题标题】:Kotlin coroutine unit test fails with "Module with the Main dispatcher had failed to initialize"Kotlin 协程单元测试失败并显示“带有主调度程序的模块无法初始化”
【发布时间】:2020-02-06 18:43:29
【问题描述】:

在为使用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()
                }
        }
    }

此外,当我删除 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)

【问题讨论】:

    标签: unit-testing kotlin kotlin-coroutines


    【解决方案1】:

    我遇到了同样的问题,我在这里找到了解决方案kotlinx.coroutines 解决方案是使用 Coroutines 1.1.0 版本,我试了一下,现在可以正常使用了

    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.0"
    

    【讨论】:

      【解决方案2】:

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

      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
                  // ...
              }
          }
      }
      

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

      【讨论】:

      【解决方案3】:

      在我的例子中,我已经为单元测试设置了主协程调度程序,但仍然不时看到一些错误。

      我已添加到 build.gradle 中,如 here:

      android {
        // ...
        testOptions { 
          unitTests.returnDefaultValues = true
        }
      }
      

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

      【讨论】:

        【解决方案4】:

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

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

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

        确保您的 Gradle 文件具有协程测试依赖项

        “org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutine_version”

        解决方案 1 - 不可扩展

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

        val dispatcher = TestCoroutineDispatcher()
        
        @Before
        fun setup() {
            Dispatchers.setMain(dispatcher)
        }
        
        @After
        fun tearDown() {
            Dispatchers.resetMain()
        }
        

        注意:您也可以将Dispatchers.Main 作为存储库的构造函数依赖项传递为CoroutineDispatcher,以防万一。建议不要在存储库/视图模型等上硬编码您的调度程序WATCH-THISPLEASEEEEEEEE

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

        解决方案 2 - 可扩展 [使用它 - Google 使用它]

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

        @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()
            }
        }
        

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

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

        @get:Rule
        val coroutineRule = MainCoroutineRule()
        

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

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

        你也可以使用Dispatchers.UnconfinedLINK

        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
        

        可以如下添加

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

        快乐编码。 . . .

        【讨论】:

        • 不错的答案,考虑到单元测试的上下文,与解决方案 2 相比,您认为解决方案 3 是一种简单且足够好的方法吗?
        【解决方案5】:

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

        Dispatchers.setMain(Dispatchers.Unconfined)
        

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

        【讨论】:

          【解决方案6】:

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

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

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

          【讨论】:

          • 它需要使用 Dispatchers.setMain(mainThreadSurrogate) 模拟主线程。还需要添加 kotlinx-coroutines-test 作为依赖
          猜你喜欢
          • 1970-01-01
          • 2011-07-11
          • 1970-01-01
          • 1970-01-01
          • 2017-08-03
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2022-01-11
          相关资源
          最近更新 更多