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