Testing in Jetpack Compose: Unit Tests and UI Tests for Composables
Mircea Ioan Soit
Senior Android Developer | Business Owner | Contractor | Team Builder
Part of the series "Android Development Series by Mircea Ioan Soit"
Writing tests for your app is crucial to ensuring its reliability, maintainability, and overall quality. Jetpack Compose brings some changes to how we traditionally test Android UIs, offering powerful tools to write both unit tests and UI tests for composables.
In this article, we’ll explore how to test your composables in Jetpack Compose, focusing on both unit testing and UI testing approaches.
1. Why Testing in Compose is Different
Jetpack Compose is a declarative UI framework, which means the UI is built based on state. This declarative nature changes the way we approach testing. Instead of interacting with Activity or Fragment views, we now test composables directly and validate their behavior based on the state they are rendering.
Compose offers the following testing tools:
2. Unit Testing Composables
Unit tests in Compose focus on testing the logic and behavior of individual composables, ensuring they correctly reflect the state passed to them.
a) Setting Up Compose Unit Testing
To begin testing, include the necessary dependencies in your build.gradle:
testImplementation "androidx.compose.ui:ui-test-junit4:<compose-version>"
testImplementation "androidx.compose.ui:ui-tooling:<compose-version>"
You’ll typically test your composables using the createComposeRule() test rule. Here’s a basic example.
b) Basic Unit Test for a Composable
Let’s create a simple Greeting composable and write a unit test for it:
@Composable
fun Greeting(name: String) {
Text(text = "Hello, $name!")
}
@Test
fun greetingDisplaysCorrectName() {
val composeTestRule = createComposeRule()
composeTestRule.setContent {
Greeting("Compose")
}
composeTestRule
.onNodeWithText("Hello, Compose!")
.assertExists()
}
In this test:
c) Testing State in Composables
You can also test how your composable behaves when state changes:
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) }
Column {
Text("Count: $count")
Button(onClick = { count++ }) {
Text("Increment")
}
}
}
@Test
fun counterIncrementsWhenButtonClicked() {
val composeTestRule = createComposeRule()
composeTestRule.setContent {
Counter()
}
// Verify initial state
composeTestRule
.onNodeWithText("Count: 0")
.assertExists()
// Simulate button click
composeTestRule
.onNodeWithText("Increment")
.performClick()
// Verify state after click
composeTestRule
.onNodeWithText("Count: 1")
.assertExists()
}
Here, we verify that the count is initially 0 and then simulate a button click to check if the counter updates to 1. Compose makes it easy to simulate user interactions such as clicks or text input during unit testing.
3. UI Testing in Jetpack Compose
UI testing is essential for validating how users interact with your app. It checks that the app’s UI responds correctly to various actions and ensures the correct UI elements are rendered.
领英推荐
Compose uses Espresso-like APIs to test the UI. It includes a set of tools that let you interact with the composables and perform actions, like clicking buttons or entering text.
a) Basic Setup for UI Tests
Add the following dependencies to your build.gradle for UI testing:
androidTestImplementation "androidx.compose.ui:ui-test-junit4:<compose-version>"
androidTestImplementation "androidx.compose.ui:ui-tooling:<compose-version>"
You will also need to use createAndroidComposeRule() to create a testing environment that simulates real UI interactions.
b) Writing a UI Test for a Composable
Let’s write a UI test for a simple login form:
@Composable
fun LoginForm(onLoginClick: (String) -> Unit) {
var username by remember { mutableStateOf("") }
Column {
TextField(value = username, onValueChange = { username = it })
Button(onClick = { onLoginClick(username) }) {
Text("Login")
}
}
}
@Test
fun loginButtonDisplaysCorrectUsername() {
val composeTestRule = createAndroidComposeRule<MainActivity>()
composeTestRule.setContent {
LoginForm {}
}
// Enter text in the username field
composeTestRule
.onNodeWithText("Username")
.performTextInput("Mircea")
// Click the login button
composeTestRule
.onNodeWithText("Login")
.performClick()
// Verify the username was entered
composeTestRule
.onNodeWithText("Mircea")
.assertExists()
}
In this UI test:
c) Testing Navigation Between Composables
When testing navigation in Compose, you want to ensure that the correct screen appears after an action, such as clicking a button.
@Composable
fun HomeScreen(navController: NavController) {
Button(onClick = { navController.navigate("details") }) {
Text("Go to Details")
}
}
@Composable
fun DetailsScreen() {
Text("Details Screen")
}
@Test
fun navigateToDetailsScreen() {
val composeTestRule = createAndroidComposeRule<MainActivity>()
composeTestRule.setContent {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "home") {
composable("home") { HomeScreen(navController) }
composable("details") { DetailsScreen() }
}
}
// Simulate button click to navigate to details
composeTestRule
.onNodeWithText("Go to Details")
.performClick()
// Verify navigation occurred
composeTestRule
.onNodeWithText("Details Screen")
.assertExists()
}
This test simulates navigation from a HomeScreen to a DetailsScreen. After clicking the button, the test checks if the "Details Screen" text is displayed, confirming that the navigation was successful.
4. Testing Complex Composables
You may have more complex composables involving animations, gestures, or conditional rendering. Jetpack Compose offers various testing utilities to handle these cases:
Example: Testing AnimatedVisibility
@Composable
fun AnimatedMessage(isVisible: Boolean) {
AnimatedVisibility(visible = isVisible) {
Text("Hello Compose!")
}
}
@Test
fun animatedMessageVisibility() {
val composeTestRule = createComposeRule()
composeTestRule.setContent {
var isVisible by remember { mutableStateOf(false) }
AnimatedMessage(isVisible = isVisible)
}
// Initially, the text should not be visible
composeTestRule
.onNodeWithText("Hello Compose!")
.assertDoesNotExist()
// Change visibility and verify the text appears
composeTestRule.runOnIdle { isVisible = true }
composeTestRule
.onNodeWithText("Hello Compose!")
.assertExists()
}
This example tests if the composable’s content becomes visible when the state changes, simulating an animation toggle.
5. Best Practices for Testing in Compose
6. Conclusion: Making Testing a Habit
Testing your composables is essential for building robust Android apps in Jetpack Compose. With the tools provided by Compose for unit testing and UI testing, you can ensure your UI components behave as expected under various conditions.