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

Compose UI测试因Idling Resource超时失败(疑似存在无限重组)

Compose UI测试因Idling Resource超时失败(疑似存在无限重组)

嘿,我仔细看了你的测试代码和PersonCard实现,这个超时问题十有八九是无限重组循环导致的——Compose一直在重复重组,根本停不下来,所以Espresso的空闲资源检查超时了。咱们来一步步解决:

问题根源

你在PersonCard里创建了几个关键对象,但没给它们加上remember修饰,导致每次Compose重组时都会生成新的实例,进而触发新一轮重组,形成循环:

  • ImageRequest每次重组都会新建,导致rememberAsyncImagePainter的model参数变化
  • placeholderPainter没有用remember包裹,每次重组都会重新创建
  • colorFilter的计算没有依赖remember,每次重组都会重新计算,可能间接触发状态变化

修复方案

给这些易变的对象加上remember,并指定正确的依赖项,确保只有当依赖的状态真的变化时才重建:

修改后的PersonCard代码

@Composable
fun PersonCard(
    modifier: Modifier = Modifier,
    person: Credit,
    navController: NavController? = null,
) {
    Column(
        modifier
            .padding(TMDb_4_dp)
            .clickable {
                navController?.navigate("${MainDestinations.TMDB_PERSON_ROUTE}/${person.id}")
            },
        horizontalAlignment = Alignment.CenterHorizontally,
    ) {
        Card(
            shape = CircleShape,
            elevation = TMDb_8_dp,
            modifier = Modifier.size(TMDb_120_dp),
        ) {
            // 仅在person的profileUrl变化时重建ImageRequest
            val request = remember(person.profileUrl) {
                ImageRequest.Builder(LocalContext.current)
                    .data(person.profileUrl)
                    .crossfade(true)
                    .transformations(CircleTopCropTransformation())
                    .build()
            }
            
            // 仅在person的gender变化时重建placeholderPainter
            val placeholderPainter = remember(person.gender) {
                rememberVectorPainter(person.gender.placeholderIcon)
            }
            
            val painter = rememberAsyncImagePainter(
                model = request,
                error = placeholderPainter,
                placeholder = placeholderPainter,
            )
            
            // 仅在painter的状态变化时重新计算colorFilter
            val colorFilter = remember(painter.state) {
                when (painter.state) {
                    is AsyncImagePainter.State.Error,
                    is AsyncImagePainter.State.Loading -> ColorFilter.tint(
                        MaterialTheme.colors.imageTint,
                    )
                    else -> null
                }
            }

            Image(
                painter = painter,
                colorFilter = colorFilter,
                contentDescription = stringResource(
                    id = R.string.person_content_description,
                    person.name,
                    person.role,
                ),
                contentScale = ContentScale.FillHeight,
            )
        }

        Text(
            text = person.name,
            style = MaterialTheme.typography.subtitle1.copy(fontWeight = FontWeight.SemiBold),
            maxLines = 1,
            overflow = TextOverflow.Ellipsis,
            modifier = Modifier.padding(top = TMDb_4_dp),
        )
        Text(
            text = person.role,
            style = MaterialTheme.typography.subtitle2.copy(
                fontWeight = FontWeight.Normal,
                fontStyle = FontStyle.Italic,
            ),
            maxLines = 1,
            overflow = TextOverflow.Ellipsis,
            modifier = Modifier.padding(top = TMDb_2_dp),
        )
    }
}

额外的测试小提示

测试里获取字符串的方式没问题,但可以考虑在setContent里直接用stringResource来生成预期的内容描述,避免依赖activity的上下文:

onNodeWithContentDescription(
    stringResource(
        R.string.person_content_description,
        "name",
        "role",
    ),
).assertIsDisplayed()

这样修改后,Compose就不会陷入无限重组循环了,你的测试应该就能顺利通过啦!

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.07 07:29:32