Kotlin协同程序单元测试未通过,“带有主调度程序的模块未能初始化”

p8ekf7hl  于 2023-01-31  发布在  Kotlin
关注(0)|答案(7)|浏览(169)

当运行使用withContext(Dispatchers.Main)的kotlin suspend方法的单元测试时,测试方法失败,但有以下例外:
我的协同程序库版本是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)
wqsoz72f

wqsoz72f1#

当运行测试时,例如为ViewModel运行协程时,您最有可能遇到以下异常
java.lang.IllegalStateException:具有Main调度程序的模块未能初始化。对于测试,可以使用kotlinx-coroutines-test模块中的Dispatchers.setMain
这背后的原因是在测试环境中缺少Looper.getMainLooper(),而实际应用程序中存在Looper.getMainLooper()。要解决这个问题,您需要用TestCoroutineDispatcher替换主调度程序
确保您的Gradle文件具有协同程序测试依赖关系
"org. jetbrains. kotlinx:kotlinx协同程序-测试:$协同程序版本"

    • 解决方案1**-不可扩展

在测试类上定义以下内容-〉使用@ExperimentalCoroutinesApi注解类

val dispatcher = TestCoroutineDispatcher()

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

@After
fun tearDown() {
    Dispatchers.resetMain()
}

注意:如果您有CoroutineDispatcher,您也可以将Dispatchers.Main作为仓库的构造函数依赖项进行传递。建议不要在仓库/视图模型等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()
        }
    }

如果您需要有关上述实用程序类的说明,请参阅以下代码实验
在你的测试类中,只需要添加以下代码行,你就可以开始了:

@get:Rule
    val coroutineRule = MainCoroutineRule()

我想如果你有很多测试类,你就会明白为什么它是可伸缩的。

    • 解决方案3**[我希望您不会到达此处]

您也可以使用Dispatchers.UnconfinedLINK
一种不局限于任何特定线程的协同程序调度程序。它在当前调用帧中执行协同程序的初始延续,并使协同程序在相应的挂起功能所使用的线程中恢复,而不要求任何特定的线程策略。在这种调度程序中启动的嵌套协同程序形成一个事件循环,以避免堆栈溢出。
您可以按如下方式添加它

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

快乐编码......

nvbavucw

nvbavucw2#

您没有访问Dispatchers. Main的权限,请在单元测试中
参见https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-test/
Dispatchers.Main Delegation部分详细解释了您需要做什么。

pn9klfpd

pn9klfpd3#

在我的例子中,我已经为单元测试设置了主协程调度器,但仍然不时地看到一些错误。
我已经添加到构建.gradle在这里:

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

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

yshpjwxd

yshpjwxd4#

现在你可以把这个添加到你的测试中:

Dispatchers.setMain(Dispatchers.Unconfined)

或其他调度程序..这是实验但它工作

t0ybt7op

t0ybt7op5#

这是因为缺少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),它们在任何测试之前被调用,以便创建主调度程序。

baubqpgj

baubqpgj6#

我遇到了同样的问题,我在这里找到了解决方案kotlinx.coroutines,解决方案是使用协程1. 1. 0版,我尝试了一下,现在它工作得很完美

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

q35jwt9p7#

TestCoroutineDispatcher是现在deprecated.这建议的方法是到使用UnconfinedTestDispatcher象下面

// Reusable JUnit4 TestRule to override the Main dispatcher
@ExperimentalCoroutinesApi
class MainDispatcherRule(
    private val testDispatcher: TestDispatcher = UnconfinedTestDispatcher(),
) : TestWatcher() {
    override fun starting(description: Description) {
        Dispatchers.setMain(testDispatcher)
    }

    override fun finished(description: Description) {
        Dispatchers.resetMain()
    }
}

参见设置主调度程序

相关问题