android/codelab-android-compose

[TestingCodelab] (Optional Exercise): How to access state in RallyApp

ktprograms opened this issue ยท 6 comments

Hi, in the hints for Step 7, it says

The scope of the test needs to include the state, which is owned by RallyApp.

I looked at the implementation of RallyApp, and I can see that the state is stored inside the component, so I don't know how I can access it to assert on its value.

Am I supposed to modify the RallyApp component to move the state elsewhere, and if so, where? In a ViewModel?

This Worked for me

fun rallyTopAppBarTest_tabSelectionChangesSelectedScreen() {

    val allScreens = RallyScreen.values().toList()

    var currentScreen by mutableStateOf(RallyScreen.Overview)

    composeTestRule.setContent {

        RallyTopAppBar(
            allScreens = allScreens,
            onTabSelected = {
                currentScreen = it
            },
            currentScreen = currentScreen
        )
    }

    composeTestRule.onNode(hasContentDescription(RallyScreen.Bills.name)).performClick()
    assert(currentScreen.name == RallyScreen.Bills.name)
}

@gagandeep3458
Thank you give to me a nice idea, your solution is awesome ๐Ÿ‘
I also thought another method too, that is to use parent composable RallyApp

composeTestRule.setContent {
    RallyTheme {
        RallyApp()
    }
}

composeTestRule
    .onNodeWithContentDescription(RallyScreen.Bills.name)
    .performClick()
    .assertIsSelected()

@juyeop03
That's a nice solution. But it can be improved so that the test verifies that clicking on any of the tabs will change the selection.
Like this:

composeTestRule.setContent {
    RallyApp()
}

val allScreens = RallyScreen.values().toList()

for (screen in allScreens) {
    composeTestRule
        .onNodeWithContentDescription(screen.name)
        .performClick()
        .assertIsSelected()
}

@gagandeep3458 Thank you. I have enhanced your solution to handle any combination of screens:

        val allScreens = RallyScreen.values().toList()
        val selectedScreen = allScreens.random() //pick a random screen name
        val anotherScreen = allScreens.filterNot { it == selectedScreen }.random() //pick a random screen excluding selectedScreen
        var currentScreen by mutableStateOf(selectedScreen)

        composeTestRule.setContent {
            RallyTopAppBar(
                allScreens = allScreens,
                onTabSelected = {
                    currentScreen = it
                },
                currentScreen = currentScreen
            )
        }

        composeTestRule.onNodeWithContentDescription(anotherScreen.name)
            .performClick()
            .assertIsSelected()
    }

This is weird - I'm doing the following:

val allScreens = RallyScreen.entries //mock the screens
        var selectedScreen: RallyScreen? = null
        composeTestRule.setContent {
            RallyTheme {
                RallyTopAppBar(
                    allScreens = allScreens,
                    onTabSelected = { screen -> selectedScreen = screen},
                    currentScreen = RallyScreen.Accounts
                )
            }
        }

        composeTestRule.onNodeWithContentDescription(RallyScreen.Overview.name).performClick()
        composeTestRule.onNodeWithContentDescription(RallyScreen.Overview.name).assertIsSelected()
        assertEquals(RallyScreen.Overview, selectedScreen)

assertEquals is true, but the assertIsSelected fails.