You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 19:53:14