LaunchedEffect中的delay导致Espresso测试失败问题求助
问题:Compose测试中LaunchedEffect的delay导致测试停滞崩溃
我编写了如下测试代码:
@get:Rule(order = 1) val composeRule: ComposeRule = createAndroidComposeRule() @Test fun test() { composeRule.setContent { var text by remember { mutableStateOf("test start") } LaunchedEffect(key1 = Unit, block = { text = "step 1" delay(200) text = "step 2" }) Text(text = text, modifier = Modifier.testTag("test")) } composeRule.onNodeWithTag("test").assertTextEquals("step 1") }
如预期,Step 1几乎立即显示,但测试在delay处停滞直至崩溃,报错信息如下:
androidx.compose.ui.test.junit4.android.ComposeNotIdleException: Idling resource timed out: possibly due to compose being busy. IdlingResourceRegistry has the following idling resources registered: - [busy] androidx.compose.ui.test.junit4.ComposeIdlingResource@a974589 All registered idling resources: Compose-Espresso link at androidx.compose.ui.test.junit4.EspressoLink_androidKt.rethrowWithMoreInfo(EspressoLink.android.kt:136) at androidx.compose.ui.test.junit4.EspressoLink_androidKt.runEspressoOnIdle(EspressoLink.android.kt:110) at androidx.compose.ui.test.junit4.EspressoLink.runUntilIdle(EspressoLink.android.kt:79) at androidx.compose.ui.test.AndroidComposeUiTestEnvironment.waitForIdle(ComposeUiTest.android.kt:336) at androidx.compose.ui.test.AndroidComposeUiTestEnvironment.access$waitForIdle(ComposeUiTest.android.kt:228) at androidx.compose.ui.test.AndroidComposeUiTestEnvironment$AndroidComposeUiTestImpl.waitForIdle(ComposeUiTest.android.kt:406) at androidx.compose.ui.test.AndroidComposeUiTestEnvironment$AndroidComposeUiTestImpl.setContent(ComposeUiTest.android.kt:485) at androidx.compose.ui.test.junit4.AndroidComposeTestRule.setContent(AndroidComposeTestRule.android.kt:340) at pl.shvagier.district.ContactTest.test(ContactTest.kt:51) ... 54 trimmed
补充说明
我发现基类测试中使用了StandardTestDispatcher作为主线程调度器。之前未推进时间导致测试停滞并不意外,但现在无异常抛出,可LaunchedEffect仍在第一个挂起点停止。
解决方案
问题根源
使用StandardTestDispatcher时,协程的时间不会自动推进,delay(200)会一直处于挂起状态。Compose的空闲资源检测会认为界面持续忙碌,最终超时抛出ComposeNotIdleException;即使没有抛出异常,LaunchedEffect也会卡在挂起点,无法继续执行后续逻辑。
修复步骤与代码示例
需要手动控制测试调度器的时间推进,确保协程任务能继续执行。以下是修改后的测试代码:
@get:Rule(order = 1) val composeRule: ComposeRule = createAndroidComposeRule() // 若基类已初始化testDispatcher,直接引用即可 private val testDispatcher = StandardTestDispatcher() @Test fun test() { composeRule.setContent { // 将测试调度器注入Compose的协程作用域 CompositionLocalProvider(LocalCoroutineScope provides CoroutineScope(testDispatcher)) { var text by remember { mutableStateOf("test start") } LaunchedEffect(key1 = Unit) { text = "step 1" delay(200) text = "step 2" } Text(text = text, modifier = Modifier.testTag("test")) } } // 断言Step 1的显示状态 composeRule.onNodeWithTag("test").assertTextEquals("step 1") // 推进时间,触发delay后的代码执行 testDispatcher.advanceTimeBy(200) // 或使用runCurrent()执行当前所有待处理的协程任务(适合不需要等待完整delay时长的场景) // testDispatcher.runCurrent() // 可选:断言Step 2的显示状态 composeRule.onNodeWithTag("test").assertTextEquals("step 2") }
额外说明
- 如果基类已将
StandardTestDispatcher设置为主线程调度器,需确保Compose的协程使用该调度器,通过LocalCoroutineScope注入是常用方式。 runCurrent()会立即执行所有当前可运行的协程任务,适合验证delay前的状态;advanceTimeBy()会模拟时间流逝,触发delay到期后的代码逻辑。- 若仅需测试
step 1的状态,也需调用testDispatcher.runCurrent()确保协程任务执行到挂起点前,避免Compose因协程挂起判定为忙碌状态。
内容的提问来源于stack exchange,提问作者Karol Kulbaka
相关产品推荐
相关产品推荐

