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

Jetpack Compose测试单独运行通过但批量执行时触发无限组合循环错误

Jetpack Compose测试单独运行通过但批量执行时触发无限组合循环错误

我之前在项目里也碰到过几乎一模一样的问题——单测跑起来全绿,批量执行或者生成Jacoco报告就报AppNotIdleException,提示Compose一直无法进入idle状态。结合你的代码和现象,咱们来拆解下可能的原因和对应的解决办法:

可能的原因分析

  1. 测试隔离不彻底:Robolectric批量执行时,前一个测试的Compose状态、ViewModel Store或者Looper任务可能没被完全清理,干扰了当前测试的组合流程。
  2. ViewModel初始化的隐式依赖:你的DocumentCheckLoadingScreen里给documentCheckLoadingViewModel设了默认参数viewModel(),虽然测试时传了mock,但批量运行时可能因为测试间的ViewModel Store残留,导致实际创建了真实的ViewModel而非使用mock?
  3. InfiniteCircularIndicator的潜在问题:这个自定义加载指示器的内部实现可能有坑——比如在Robolectric环境下,动画逻辑(比如animate*AsState、LaunchedEffect里的循环任务)没有被正确处理,导致无限触发重组。
  4. Compose Idle检测的超时逻辑:批量执行时,测试框架的idle等待机制可能因为前序测试的残留异步任务,导致当前测试的断言还没完成就触发了超时。

针对性解决方案

试试下面这些办法,大概率能解决问题:

  • 强制测试间的状态清理:在测试类里加@Before和@After方法,确保每个测试前后Compose状态完全隔离:
@Before
fun setup() {
    composeTestRule.setContent { /* 空内容,重置前一个测试的Compose树 */ }
    composeTestRule.waitForIdle()
}

@After
fun teardown() {
    composeTestRule.setContent {}
    composeTestRule.waitForIdle()
}
  • 避免Composable的ViewModel默认参数依赖:把DocumentCheckLoadingScreen的documentCheckLoadingViewModel参数改成非可选,或者用依赖注入框架(比如Hilt)来管理ViewModel,确保测试时能完全替换成mock,避免默认的viewModel()在批量环境下的意外行为。

  • 定位InfiniteCircularIndicator的问题:暂时把InfiniteCircularIndicator替换成一个静态的Composable(比如一个简单的Box),再跑批量测试。如果不报错了,那就是这个指示器的实现有问题——比如检查它内部有没有在measure/layout lambda里修改状态,或者动画在Robolectric下的适配问题。可以给指示器加个isInTest参数,测试时禁用动画逻辑。

  • 显式等待Compose Idle:在断言前主动调用composeTestRule.waitForIdle(),确保Compose树稳定后再执行断言:

composeTestRule.setContent {
    // ... 你的Composable代码
}
// 显式等待idle
composeTestRule.waitForIdle()
// 然后执行断言
composeTestRule.onNodeWithText(title).assertIsDisplayed()
// ... 其他断言
  • 调整Robolectric的Looper模式:在测试类上添加@LooperMode(LooperMode.Mode.PAUSED)注解,Robolectric的PAUSED模式会更严格地处理线程和任务,避免批量执行时的Looper任务堆积。

你的代码参考

测试类代码

@RunWith(RobolectricTestRunner::class)
internal class DocumentCheckLoadingScreenTest {

    @get:Rule
    val composeTestRule = createComposeRule()

    @Test
    fun testDocumentCheckLoadingScreenElementsVisibility() {
        val title = "Loading Title"
        val loadingMessage = "Loading your document..."
        
        composeTestRule.setContent {
            DocumentCheckLoadingScreen(
                title = title,
                loadingMessage = loadingMessage,
                documentCheckLoadingViewModel = mockk(relaxed = true)
            )
        }

        composeTestRule.onNodeWithText(title).assertIsDisplayed()
        composeTestRule.onNodeWithText(loadingMessage).assertIsDisplayed()
        composeTestRule.onNodeWithText("Please Wait").assertIsDisplayed()
        composeTestRule.onNodeWithContentDescription("Loading Overlay").assertIsDisplayed()
    }
}

Composable代码

@Composable
internal fun DocumentCheckLoadingScreen(
    title: String = stringResource(R.string.cc_doc_check_title),
    loadingMessage: String = "",
    documentCheckLoadingViewModel: DocumentCheckLoadingViewModel = viewModel()
) {
    Column(modifier = Modifier
        .background(MaterialTheme.localSdkColors.backgroundColor)
        .fillMaxSize()) {
        Column(horizontalAlignment = Alignment.CenterHorizontally) {
            AlertDialogTitleText(
                modifier = Modifier.padding(top = 25.dp),
                text = title
            )
            Column(
                modifier = Modifier
                    .fillMaxSize()
                    .background(MaterialTheme.localSdkColors.backgroundColor),
                horizontalAlignment = Alignment.CenterHorizontally,
                verticalArrangement = Arrangement.Center
            ) {
                InfiniteCircularIndicator()
                Spacer(modifier = Modifier.height(16.dp))
                SubTitleText(
                    modifier = Modifier.padding(top = 8.dp),
                    text = loadingMessage,
                    textAlign = TextAlign.Center,
                    color = MaterialTheme.localSdkColors.headerTextColor
                )
                SubTitleText(
                    modifier = Modifier.padding(top = 8.dp).testTag("please_wait_text"),
                    text = stringResource(R.string.cc_workflow_please_wait),
                    textAlign = TextAlign.Center,
                    color = MaterialTheme.localSdkColors.headerTextColor
                )
            }
        }
    }
}

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.09 03:03:16