Skip to content
🤔 Documentation issue? Report or edit

UI Testing

Setup

Add the Compose testing library to your dependencies in build.gradle

androidTestImplementation("androidx.compose.ui:ui-test:$compose_version")
debugImplementation("androidx.compose.ui:ui-test-manifest:$compose_version")

Example

Let’s say you have the following example code, it’s a button with a text that says Hello and when you click on it, it turns to Bye

@Composable
fun TestingExample() {
    val state = remember { mutableStateOf("Hello") }
    Button(onClick = { state.value = "Bye" }) {
        Text(state.value)
    }
}

TestRule

To run this Composable in your test Code you have 2 options:

1) createComposeRule()

@Rule
@JvmField
var composeTestRule: ComposeContentTestRule = createComposeRule()

You can use this when you want to run your Composable without a specific Activity. You can then use the setContent() from the TestRule to host your Composable.

composeTestRule.setContent {
    TestingExample()
}

2) createAndroidComposeRule()

@Rule
@JvmField
var composeTestRule: ComposeContentTestRule = createAndroidComposeRule<UiTestingDemoActivity>()

You can use this when you want to start your test with a specific Activity.

Interaction with Composables

Now we want to test how the Composable reacts when we interact with it. Usually in Tests we would use Espresso for that, but you can use that only for classic Android Views and not for Composables.

The ComposeTestRule will offer a similar API

class ExampleUiTestWithAndroidComposeRule {

    @Rule
    @JvmField
    var composeTestRule: ComposeContentTestRule = createAndroidComposeRule<UiTestingDemoActivity>()

    @Test
    fun whenIClickOnButton_TheTextShouldChange() {
        composeTestRule.onNodeWithText("Hello").assertExists()
        composeTestRule.onNodeWithText("Hello").performClick()
        composeTestRule.onNodeWithText("Hello").assertDoesNotExist()
        composeTestRule.onNodeWithText("Bye").assertExists()
    }

}

As you can see above we used the TestRule methods to click on the node with the text Hello and asserted that the text changed to Bye

The TestRule offers a lot of different methods. You can find a cheatSheet here

TestTags

The test above is only half correct, because it looks for the text inside the button and not the button itself. Composables have no resource ids so we cant just use onView(withId(R.id.my_view)) to find a Composable in a test, also we can’t find Composables of a specific “type” like a button, because everything is just a Composable function.

When you can’t find a Composable by a text and you want to make it detectable in your test. Jetpack Compose offers the concept of a TestTag. testTag is a modifier that needs to be set to a Composable. It expects a string which will be used as a reference in your test.

@Composable
fun TestingExample() {
    val state = remember { mutableStateOf("Hello") }
    Button(onClick = { state.value = "Bye" }, modifier = Modifier.testTag("MyTestTag")) {
        Text(state.value)
    }
}

Now you can use onNodeWithTag(“MyTestTag”) to find the button Composable.

@Test
fun whenIClickOnButton_TheTextShouldChange() {
    composeTestRule.onNodeWithTag("MyTestTag").assertTextEquals("Hello")
    composeTestRule.onNodeWithTag("MyTestTag").performClick()
    composeTestRule.onNodeWithText("Hello").assertDoesNotExist()
    composeTestRule.onNodeWithTag("MyTestTag").assertTextEquals("Bye")
}

PrintToLog

When you want to get more information about how the node tree of a Composable looks like, you can use printToLog() on a node.

composeTestRule.onNodeWithTag("MyTestTag").printToLog("XXX")

will print to logcat:

2022-01-07 22:58:55.048 9567-9587/de.jensklingenberg.jetpackcomposeplayground D/XXX: printToLog:
Printing with useUnmergedTree = 'false'
Node #2 at (l=0.0, t=325.0, r=195.0, b=424.0)px, Tag: 'MyTestTag'
Role = 'Button'
Text = '[Hello]'
Actions = [OnClick, GetTextLayoutResult]
MergeDescendants = 'true'

See also:


Last update: April 11, 2023