UI Test with Jetpack Compose👨🏻‍💻

Arthur Henz
4 min readOct 9, 2024

--

Overview

Most developers are already familiar with the concept of automated UI testing and its importance in a project (TL;DR: avoid client UI flaws). With the use of Jetpack Compose, this task has become more accessible and simple, as it has a robust tool developed by the Android team itself. In this article, I will show you how to test a layout and provide the necessary fundamentals to test interfaces effectively.

Setup

Following the official documentation, just add these dependencies:

// Testing rules and transitive dependencies:
androidTestImplementation("androidx.compose.ui:ui-test-junit4:$compose_version")
// Required for createComposeRule() but not for createAndroidComposeRule<YourActivity>()
debugImplementation("androidx.compose.ui:ui-test-manifest:$compose_version")

This module includes the AndroidComposeTestRule. This rule allows you to define a Compose content or access the Activity. You build the rules using functions. The most commonly used is createComposeRule, but if you need access to an Activity, use createAndroidComposeRule.

The test-junit4 dependency provides test utilities for Jetpack Compose UI tests with JUnit 4, while the ui-test-manifest dependency is required to use the AndroidComposeTestRule.

The basics

We have a ComposeUITest class with a single test case, UIComponentTest(). The test object is createComposeRule(), which is used to initialize the Compose UI framework.

@RunWith(AndroidJUnit4::class)
class ComposeUITest {
@get:Rule
val composeTestRule = createComposeRule()

@Test
fun UIComponentTest() {
composeTestRule.setContent {
GenericComponent()
}

composeTestRule.onNodeWithText("Button").performClick()
composeTestRule.onNodeWithText("Clicked!").assertIsDisplayed()
}
}
  • @RunWith(AndroidJUnit4::class) specifies the use of the AndroidJUnit4 testing framework.
  • We set up a test hierarchy using the ComposeTestRule class. This rule sets up the necessary environment for your Compose UI tests and provides several utility functions.
  • composeTestRule.setContent { … } initializes the Compose UI by invoking the UI component of our choice. In this case, ComponentCreated().
  • We interact with the UI elements and trigger user actions using the androidx.ui.test library.

Note: It is important to add Modifier.testTag(“tag_name”) to the elements that will be tested in your component. This will allow us to check the exact element by accessing it through the tag.

The component we’ll consider is this one:

internal const val tagLazyColumn= "NameColumnTag"

@Composable
fun CustomComponent() {
val itemsList = listOf("Item 1", "Item 2", "Item 3", "Item 4", "Item 5", "Item 6")

LazyColumn(
modifier = Modifier
.fillMaxSize()
.testTag(tagLazyColumn)
) {
items(itemsList) { item ->
Text(
text = item,
modifier = Modifier
.padding(8.dp)
)
}
}
}

Also check your module’s build.gradle file to make sure that testInstrumentationRunner is set to the default configuration. If it isn’t, set it up so that the default configuration looks like this:

defaultConfig {
applicationId = "com.company.project"
minSdk = 30
targetSdk = 34
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
}
}

Now, let’s test the UI of a component

Now that everything is set up, let’s write the tests. Create a file in the instrumented test section with the pattern {ComponentName}Test. So, my file is the ComponentCreatedTest.

After creating it, we’ll define how this file will be executed using the @RunWith annotation and create the initial rule and the main object for handling tests, as seen earlier in this document.

@RunWith(AndroidJUnit4::class)
class CustomComponentTest{
@get: Rule
val composeTestRule = createComposeRule()
}

With a Rule configured, we’ll use another JUnit feature, the @Before annotation, which ensures that these attributes are configured before each test is run.

@Before
fun setUp() {
composeTestRule.setContent {
CustomComponent()
}
}

In addition, I recommend placing the tags somewhere in the project. This helps with maintainability.

internal const val tagLazyColumn= "NameColumnTag"

First test

  • Checks if the LazyColumn was rendered.
@Test
fun initial_component_visibility(){
composeTestRule.onNodeWithTag(tagLazyColumn).assertExists()
}

Second test

  • Checks if the item “Item 1” is visible on the screen, without scrolling, using assertExists().
@Test
fun verify_first_item_is_visible() {
composeTestRule.onNodeWithText("Item 1").assertExists()
}

Third test

  • Performs scroll in the LazyColumn of CustomComponent.
  • Checks if the last item is visible after scrolling(assertExists()).
@Test
fun scroll_through_list() {
composeTestRule.onNodeWithTag(tagLazyColumn)
.performScrollToNode(hasText("Item 6"))

composeTestRule.onNodeWithText("Item 6").assertExists()
}

TL;DR

@RunWith(AndroidJUnit4::class)
class CustomComponentTest{
@get:Rule
val composeTestRule = createComposeRule()

@Before
fun setUp() {
composeTestRule.setContent {
CustomComponent()
}
}

@Test
fun initial_component_visibility() {
composeTestRule.onNodeWithTag(tagLazyColumn).assertExists()
}

@Test
fun verify_first_item_is_visible() {
composeTestRule.onNodeWithText("Item 1").assertExists()
}

@Test
fun scroll_through_list() {
composeTestRule.onNodeWithTag(tagLazyColumn)
.performScrollToNode(hasText("Item 6"))
composeTestRule.onNodeWithText("Item 6").assertExists()
}
}

Final Thoughts

UI testing is a crucial aspect of Android application development using the declarative UI toolkit. In this article, we explored the basics of testing Jetpack Compose-based UIs and learned how to set up the testing environment, write effective tests, and interact with UI elements.

Ultimately, Jetpack Compose UI testing empowers developers to create robust and reliable UIs for their Android applications. By following the guidelines and techniques discussed in this article, developers can ensure the quality and usability of their Compose-based UIs, ultimately ensuring the expected results and a better user experience.

--

--

Arthur Henz
Arthur Henz

Written by Arthur Henz

An Android developer who loves connections and self improvement :)

No responses yet