Jetpack Compose is Android’s modern toolkit for building native UI. Testing is an essential part of the development process, and Compose provides excellent support for UI testing. ComposeTestRule
is a central component in Compose UI testing, offering a suite of APIs to interact with and assert on composables. This post will guide you through setting up ComposeTestRule
and writing effective UI tests for your Compose applications.
What is ComposeTestRule
?
ComposeTestRule
is an interface in Jetpack Compose that provides access to testing APIs specific to Compose. It allows you to:
- Find composables by text, tags, or other properties.
- Perform actions on composables, like clicks or text input.
- Assert on the state and properties of composables.
- Control the composition and synchronization of UI elements.
Why Use ComposeTestRule
?
- Comprehensive Testing: Ensures all parts of your composable UI are functioning correctly.
- Stable and Reliable: Designed to work with Compose’s reactive nature.
- Ease of Use: Simplifies writing UI tests with declarative and expressive APIs.
How to Set Up ComposeTestRule
in Jetpack Compose
Step 1: Add Dependencies
First, ensure that you have the necessary dependencies in your build.gradle
file. These dependencies include androidx.compose.ui:ui-test-junit4
, which contains ComposeTestRule
, and other testing dependencies:
dependencies {
// Compose dependencies
implementation("androidx.compose.ui:ui:$compose_version")
implementation("androidx.compose.material:material:$compose_version")
implementation("androidx.compose.ui:ui-tooling-preview:$compose_version")
debugImplementation("androidx.compose.ui:ui-tooling:$compose_version")
// Testing dependencies
androidTestImplementation("androidx.compose.ui:ui-test-junit4:$compose_version")
debugImplementation("androidx.compose.ui:ui-test-manifest:$compose_version")
// JUnit for instrumentation tests
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
}
Replace $compose_version
with the version of Compose you are using (e.g., 1.5.0
).
Step 2: Create a Test Class
Create a new Kotlin class for your UI tests, usually located in the androidTest
source set. Annotate the class with @RunWith(AndroidJUnit4::class)
:
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class MyComposeTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun myFirstComposeTest() {
// Test logic will go here
}
}
In this setup:
@RunWith(AndroidJUnit4::class)
: Specifies that the test should be run with JUnit 4.@get:Rule val composeTestRule = createComposeRule()
: Creates aComposeTestRule
instance that will manage the composition lifecycle.
Step 3: Write Your First UI Test
Inside your test function, use the composeTestRule
to set the content and interact with the UI:
import androidx.compose.material.Text
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class MyComposeTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun myFirstComposeTest() {
// Set up the content to be tested
composeTestRule.setContent {
Text("Hello, Compose!")
}
// Find the composable with the specified text and assert that it exists
composeTestRule.onNodeWithText("Hello, Compose!")
.assertExists()
}
}
Here’s a breakdown:
composeTestRule.setContent { ... }
: Sets the content of the Compose UI for the test. This is where you place your composable.composeTestRule.onNodeWithText("Hello, Compose!")
: Finds a node in the composition that contains the specified text..assertExists()
: Asserts that the node found exists in the UI.
Step 4: Perform Actions and Assertions
Compose UI tests support various actions and assertions to interact with your composables and verify their behavior:
- Clicking a button:
composeTestRule.onNodeWithText("Click Me")
.performClick()
- Entering text into a TextField:
composeTestRule.onNodeWithTag("MyTextField")
.performTextInput("Sample Text")
- Asserting text content:
composeTestRule.onNodeWithText("Result: 10")
.assertExists()
Here’s an example of a more complex test that combines these elements:
import androidx.compose.foundation.layout.Column
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performClick
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class CounterTest {
@get:Rule
val composeTestRule = createComposeRule()
@Composable
fun CounterApp() {
val counter = remember { mutableStateOf(0) }
Column {
Text(text = "Count: ${counter.value}", testTag = "counterText")
Button(onClick = { counter.value++ }, modifier = androidx.compose.ui.Modifier.testTag("incrementButton")) {
Text(text = "Increment")
}
}
}
@Test
fun testCounterIncrements() {
composeTestRule.setContent {
CounterApp()
}
// Initial count
composeTestRule.onNodeWithTag("counterText").assertExists()
composeTestRule.onNodeWithTag("counterText").assertTextEquals("Count: 0")
// Click the increment button
composeTestRule.onNodeWithTag("incrementButton").performClick()
// Assert that the count has incremented
composeTestRule.onNodeWithTag("counterText").assertTextEquals("Count: 1")
}
}
This test checks:
- The initial value of the counter is “Count: 0”.
- After clicking the “Increment” button, the counter updates to “Count: 1”.
Best Practices for Using ComposeTestRule
- Use Semantic Properties: Add
testTag
modifiers to your composables to make them easily identifiable in tests. - Avoid Thread.sleep(): Use Compose’s synchronization mechanisms instead of relying on fixed delays.
- Keep Tests Isolated: Each test should focus on a single unit of behavior.
- Run Tests Regularly: Integrate UI tests into your CI/CD pipeline.
Conclusion
ComposeTestRule
is a vital tool for writing UI tests in Jetpack Compose. By setting up ComposeTestRule
correctly and following best practices, you can ensure that your Compose UI is robust and functions as expected. Effective UI testing leads to higher-quality applications and a better user experience.