Android Compose:如何在单元测试中验证开关是打开还是关闭

问题描述 投票:0回答:3

我在可组合项中有一个 Switch:

Switch(
    checked = false,
    modifier = Modifier.testTag("mySwitch")
)

我正在尝试通过组合单元测试来验证它:

composeTestRule.onAllNodesWithTag("mySwitch")
    .assertAll(isToggleable() and isOff())

但是它失败了,但出现以下异常:

java.lang.AssertionError: Failed to assertAll((ToggleableState is defined) && (ToggleableState = 'Off'))
Found '1' nodes not matching:
1) Node #8 at (l=955.0, t=387.0, r=1054.0, b=450.0)px, Tag: 'switch'
Has 4 siblings
Selector used: 'TestTag = 'mySwitch''

显然该开关既不可切换也不能“开/关”。我还分别检查了

assertIsToggleable
assertIsOff
,但都失败了。

我已经验证 Switch 对于测试中使用的 UI 状态是可见的。

为什么我的测试失败?应该可以轻松测试一个该死的 Switch。开关正是“可切换”的定义。那么我该如何测试它,我应该使用自定义语义属性吗?


免责声明:这个问题与这个问题不同。我想验证 Switch 状态,而不是点击它(我稍后会尝试)

android unit-testing android-jetpack-compose switch-component
3个回答
5
投票

我的测试代码毕竟是正确的。

assertIs[Not]Toggleable
assertIsOff
assertIsOn
assertAll(isToggleable())
应该可以在 Switch 上使用。但由于 Compose 1.2.1 中的错误(或奇怪的未记录功能),除非您将回调作为参数传递,否则 Switch 不会被标记为“可切换”。

我通过检查

Switch.kt
的源代码发现了这一点。这是导致问题的部分:

@Composable
    @OptIn(ExperimentalMaterialApi::class)
    fun Switch(
        checked: Boolean,
        onCheckedChange: ((Boolean) -> Unit)?,
        ...
    ) {
        ...
        val toggleableModifier =
            if (onCheckedChange != null) {
                Modifier.toggleable(
                    value = checked,
                    onValueChange = onCheckedChange,
                    enabled = enabled,
                    role = Role.Switch,
                    interactionSource = interactionSource,
                    indication = null
                )
            } else {
                Modifier
            }

如您所见,除非您传递可选参数

onCheckedChange
,否则 Switch 不会变为可切换。这反过来会导致所有类似
isToggleable
的断言抛出异常,并且
isOn
isOff
断言也会发生同样的情况,因为它们还要求节点是“可切换的”。

因此,将虚拟

onCheckedChange
lambda 传递给测试代码中的开关解决了问题:

Switch(checked=True,onCheckedChange={})

0
投票

如果您不想提供

onCheckedChange
参数,您可以使用以下内容:

@Composable
fun SemanticSwitch(
    checked: Boolean,
    modifier: Modifier = Modifier,
    enabled: Boolean = true,
    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
    colors: SwitchColors = SwitchDefaults.colors()
) {
    Switch(
        modifier = modifier.semantics {
            toggleableState = if(checked) ToggleableState.On else ToggleableState.Off
        },
        checked = checked,
        onCheckedChange = null,
        enabled = enabled,
        interactionSource = interactionSource,
        colors = colors
    )
}

或者如果您只想应用修改器:

@Composable  
fun Modifier.applyCheckedSemantics(checked: Boolean): Modifier = this.semantics { 
    toggleableState = if(checked) ToggleableState.On else ToggleableState.Off  
}

用途:

Switch(  
    modifier = Modifier  
        .testTag("mySwitch")  
        .applyCheckedSemantics(switchChecked),  
    checked = switchChecked,  
    onCheckedChange = null  
)

-1
投票

测试

您可以在测试中使用

.assertIsOn()

private val checked = mutableStateOf(false)

@Before
fun launchContent() {
    composeTestRule.setContent {
        SharedComponentsTheme {
            Switch(
                checked = checked.value,
                onCheckedChange = { checked.value = !checked.value }
            )
        }
    }
}

@Test
fun testSwitchOn() {
    checked.value = false
    composeTestRule.onNodeWithTag("SWITCH_TAG")
        .performTouchInput { click() }
        .assertIsOn()
}

代码

在代码中,您需要将

toggleableState
设置为
On
Off

import androidx.compose.ui.state.ToggleableState.On
import androidx.compose.ui.state.ToggleableState.Off
import androidx.compose.ui.semantics.Role.Switch

@Composable
fun Switch(
    checked: Boolean,
    onCheckedChange: (Boolean) -> Unit,
) {
    androidx.compose.material3.Switch(
        modifier = Modifier
            .clearAndSetSemantics {
                testTag = "SWITCH_TAG",
                role = Switch,
                toggleableState = if (checked) {
                    On
                } else {
                    Off
                }
            },
        checked = checked,
        onCheckedChange = { onCheckedChange(it) }
    )
}
© www.soinside.com 2019 - 2024. All rights reserved.