DEV Community

Cover image for Testing Different Navigation Options with Compose
Eevis
Eevis

Posted on • Originally published at eevis.codes

Testing Different Navigation Options with Compose

One part of creating accessible Android apps is to provide alternative navigation options. Some examples include touch (or pointer) input, keyboard navigation, switch navigation, and screen reader navigation. But how can you write tests for these different ways of navigation?

In this blog post, I'll share some examples of how to do that. I'm using an old demo project about making graphs more accessible and demonstrating how to write tests for the different elements I've explained with that demo project.

About the Code We're Using

As mentioned, I'm using an old demo project as the basis for the tests. In short, it contains a graph displaying data and is navigable with touch input, keyboard, switch device, and screen reader. The additional buttons for changing the highlighted sections in the chart also work for someone who has, for example, tremors in their hands or reduced dexterity.

If you want to learn more about how I built the UI and the reasons behind the decisions, I've added links to all the blog posts in the Related Blog Posts section.

Alright, let's get to writing tests!

Setting Up The Tests

Let's first set up the tests by creating a test class in the androidTest-package, defining composeTestRule, and adding a setup function that runs before each test:

class GraphScreenTest {
    @get:Rule
    val composeTestRule = createComposeRule()

    @Before
    fun setupTests() {
        composeTestRule.setContent {
            GraphExampleTheme {
                GraphScreen()
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Another part of the setup phase is deciding how we will retrieve the elements we use for testing. In this case, I decided to use test tags for simplicity, and I've defined a TestTags-object for sharing between the UI and tests. This solution is straightforward and might not be your choice in a production app, but as this is a demo, it uses the most explicit option.

You can find all the changes from this blog post in this commit.

Touch Navigation

The first tests we're going to write are about touch interaction. First, here's a short video of how things work in the UI when a user uses their finger to drag over the chart:

So, when a user moves their pointer input around in the graph, a box with values appears in the bottom right corner of the UI, displaying the values for the selected year.

Let's first get the elements we're going to use in the test:

@Test
fun touchInteractionsWorkCorrectly() {
    val labels = 
        composeTestRule.onNode(hasTestTag(TestTags.labelsTestTag))
    val chart = 
        composeTestRule.onNode(hasTestTag(TestTags.chartTestTag))
}
Enter fullscreen mode Exit fullscreen mode

Why these two? First, the labels variable is the one we're using to check if things work correctly. It contains the information that changes, so by checking the year, we can ensure that navigation works correctly. Second, the chart is the one we're interacting with.

The actual tests look like this:

@Test
fun touchInteractionsWorkCorrectly() {
    ...

    labels.assertIsNotDisplayed()

    // Navigate forward
    chart.performTouchInput {
        swipeRight(startX = 0f, endX = 30f)
    }

    labels.assertIsDisplayed()
    labels.onChildren().assertAny(hasText("2015"))

    // Navigate forward
    chart.performTouchInput {
        swipeRight(startX = 30f, endX = 70f)
    }

    labels.onChildren().assertAny(hasText("2016"))
}
Enter fullscreen mode Exit fullscreen mode

We first assert that the labels are not visible because that's how the UI is before navigation actions. After that, we perform touch input by swiping right, asserting that the correct year (in this case, "2015") is displayed in the labels component.

The numbers we use for swipeRight are based on the code, and the 35-pixel swipe is still inside the area used in the code for deciding what year is shown. In the same way, the second swipe from 30 to 70 moves from the first year to the second year.

Alright, we've written a test for touch/pointer input navigation. Next, we want to write tests for keyboard navigation.

Keyboard Navigation

The following video demonstrates what the keyboard navigation looks like on the graph when a user presses the "next" button (right arrow in this case) to navigate forward in the graph:

To test the keyboard navigation, we'll need a similar setup as for the touch/pointer interactions:


@Test
fun keyboardNavigationWorksCorrectly() {
    val labels = 
        composeTestRule.onNode(hasTestTag(TestTags.labelsTestTag))
    val chart = 
        composeTestRule.onNode(hasTestTag(TestTags.chartTestTag))

    labels.assertIsNotDisplayed()

}
Enter fullscreen mode Exit fullscreen mode

For this test, we define the same variables (labels and chart) and then assert that the labels component is not displayed.

Next, we'll need to perform some keyboard input actions. We can do that with performKeyInput and pressKey:

chart.performKeyInput {
    pressKey(Key.DirectionRight)
}
Enter fullscreen mode Exit fullscreen mode

Key.DirectionRight is the key for the right-pointing arrow. For the test, we want to first navigate forward some years, assert that the position is correct, and then navigate back and assert the year:

@Test
fun keyboardNavigationWorksCorrectly() {
   ...

    // Navigate forward
    chart.performKeyInput {
        pressKey(Key.DirectionRight)
        pressKey(Key.DirectionRight)
        pressKey(Key.DirectionRight)
        pressKey(Key.DirectionRight)
        pressKey(Key.DirectionRight)
        pressKey(Key.DirectionRight)
    }

    labels.onChildren().assertAny(hasText("2020"))

    // Navigate back
    chart.performKeyInput {
        pressKey(Key.DirectionLeft)
        pressKey(Key.DirectionLeft)
        pressKey(Key.DirectionLeft)
    }

    labels.onChildren().assertAny(hasText("2017"))
}
Enter fullscreen mode Exit fullscreen mode

These lines assert that the keyboard navigation works correctly. The last thing to test in the context of this blog post is the on-screen button navigation.

Navigation Using On-Screen Buttons

The previous videos didn't display the on-screen buttons because they were recorded before adding them. Here's a video with the buttons and how the navigation works:

So, to test, again, we'll have similar - but not exactly the same! - setup:

@Test
fun buttonNavigationWorksCorrectly() {
    val labels = 
        composeTestRule.onNode(hasTestTag(TestTags.labelsTestTag))
    val leftButton =
        composeTestRule.onNode(hasTestTag(TestTags.leftButtonTestTag))
    val rightButton = 
        composeTestRule.onNode(hasTestTag(TestTags.rightButtonTestTag))

    labels.assertIsNotDisplayed()
}
Enter fullscreen mode Exit fullscreen mode

This time, besides getting the labels, we don't need the chart component at all. Instead, we'll get a reference to both buttons on the screen.

Next, we want to navigate forward by clicking the button and asserting that the year on the label is correct. After that, we do some forward and backward navigation to ensure both buttons work correctly:

@Test
fun buttonNavigationWorksCorrectly() {
    ...

    // Navigate forward
    rightButton.performClick()

    labels.assertIsDisplayed()

    labels.onChildren().assertAny(hasText("2015"))

    // Navigate forward
    rightButton.performClick()
    rightButton.performClick()
    rightButton.performClick()
    rightButton.performClick()

    // Navigate back
    leftButton.performClick()
    leftButton.performClick()

    labels.onChildren().assertAny(hasText("2017"))
}
Enter fullscreen mode Exit fullscreen mode

And this is how we can test the on-screen button navigation in the graph.

Wrapping Up

In this blog post, we've written tests for three different navigation styles: Touch/pointer input, keyboard navigation, and on-screen button navigation. This way, we've tested that users using different navigation methods and tools can use the app.

Do you test for these interactions and navigation alternatives? If so, do you have any tips on testing them?

Links

Links in the Blog Post

Related Blog Posts

Top comments (0)