Kotlin协程单元测试因“主调度程序的模块初始化失败”而失败

时间:2019-10-09 12:17:02

标签: unit-testing kotlin kotlin-coroutines

在使用>的kotlin挂起方法的运行单元测试中,该测试方法失败,但出现以下异常:

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

示例:

withContext(Dispatchers.Main)

此外,当我移除suspend fun methodToTest() { withContext(Dispatchers.Main) { doSomethingOnMainThread() val data = withContext(Dispatchers.IO) { doSomethingOnIOThread() } } } 时,效果很好。

withContext(Dispatchers.Main)

5 个答案:

答案 0 :(得分:5)

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

Dispatchers.setMain(Dispatchers.Unconfined)

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

答案 1 :(得分:4)

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

java.lang.IllegalStateException:具有主调度程序的模块具有 初始化失败。对于测试,请从以下位置获取Dispatchers.setMain 可以使用kotlinx-coroutines-test模块

其背后的原因是真实应用程序中存在的测试环境缺少Looper.getMainLooper()。要解决此问题,您需要将主调度程序替换为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-THIS PLEASEEEEEEEE

上对您的调度程序进行硬编码。

为什么不具有可伸缩性:您将需要在每个测试类上复制并粘贴相同的代码

解决方案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.Unconfined LINK

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)

在我的情况下,我已经将主协程分配器设置为进行单元测试,但仍然不时看到一些错误。

我已经像here中一样添加到build.gradle中:

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

我再也看不到这个错误。

答案 3 :(得分:1)

您无权使用Dispatchers。主要用于单元测试

请参见https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/

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

答案 4 :(得分:0)

抛出该错误是因为缺少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
            // ...
        }
    }
}

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