https://kotlinlang.org logo
#compose
Title
# compose
r

Roberto Leinardi

05/05/2022, 4:43 PM
Hello, I'm trying to write an integration test for an Android application entirely written in Compose that has a single Activity and uses the Compose Navigation to change the screen content. I managed to properly interact and test the first screen that is shown by the navigation graph but, as soon as I navigate to a new destination, the test fails because it does not wait for the NavHost to load the new content. This is my current code for the test:
Copy code
@RunWith(AndroidJUnit4::class)
class MainActivityTest {
    @get:Rule
    val composeTestRule = createAndroidComposeRule<MainActivity>()

    @Test
    fun appStartsWithoutCrashing() {
        composeTestRule.apply {
            // Check Switch
            onNodeWithTag(FirstScreen.CONSENT_SWITCH)
                .assertIsDisplayed()
                .assertIsOff()
                .performClick()
                .assertIsOn()

            // Click accept button
            onNodeWithTag(FirstScreen.ACCEPT_BUTTON)
                .assertIsDisplayed()
                .performClick()

            // Thread.sleep(500)

            // Check we are inside the second screen
            onNodeWithTag(SecondScreen.USERNAME_TEXT_FIELD)
                .assertIsDisplayed()
        }
    }
}
I'm sure that is a timing issue because if I add a
Thread.sleep(500)
before the
onNodeWithTag(SecondScreen.USERNAME_TEXT_FIELD).assertIsDisplayed()
, the test is successful. But I would like to avoid `Thread.sleep()`s in my code. Is there a better way to tell the
composeTestRule
to wait for the NavHost to load the new content before executing proceeding with the next assertion? PS I know that would be better to test the Composables in isolation, but I really need to simulate the user input on the App using Espresso and not only test the Composable behavior.
🧵 1
a

Alex Vanyo

05/05/2022, 6:09 PM
In a case like this,
ComposeTestRule.waitForIdle
might be your best bet to wait for everything to be idle before continuing. If you haven’t seen it already, https://developer.android.com/jetpack/compose/testing#sync goes through the different APIs and methods available for avoiding those pesky
Thread.sleep
s
r

Roberto Leinardi

05/05/2022, 6:19 PM
Unfortunately I have already tried with
waitForIdle()
and it did not work
a

Alex Vanyo

05/05/2022, 6:24 PM
ComposeTestRule.waitUntil
would be your next best bet then
r

Roberto Leinardi

05/05/2022, 6:57 PM
any suggestion for the condition of the
waitUntil
?
a

Alex Vanyo

05/05/2022, 7:17 PM
Nothing stopping you from checking for whether the node exists with that tag that you want to assert on later:
Copy code
composeTestRule.waitUntil {
    composeTestRule
        .onAllNodesWithTag(SecondScreen.USERNAME_TEXT_FIELD)
        .fetchSemanticsNodes().size == 1
}
I’ll plug Jose’s recent article too which goes into more detail: https://medium.com/androiddevelopers/alternatives-to-idling-resources-in-compose-tests-8ae71f9fc473
i

Ian Lake

05/06/2022, 3:22 AM
Do you have a reference to your
NavController
in your test? Navigation only moves the current screen to
RESUMED
when the animation completes, which is exactly the kind of condition you'd want to use with
waitUntil
Navigation just uses normal Compose Animation APIs (
Crossfade
for Navigation itself,
AnimatedContent
if you are using Accompanist Navigation Animation), so
waitForIdle
should certainly work to wait for those animations to complete. I wonder if there's some one frame delay (i.e., would two back to back calls to
waitForIdle()
actually make it work)
r

Roberto Leinardi

05/06/2022, 8:34 AM
@Ian Lake I could expose the
NavController
but I suspect that there will still be some delay between the
NavController
changing the current route and the composables being actually available. But it's just a guess. @Alex Vanyo hey that actually worked! Thanks! I'm now checking Jose's medium post 🙂
29 Views