Blog Infos
, , ,

UI created with XML is traditionally tested with Espresso and UIAutomator. However, Jetpack Compose constructs UI in differently and the usual tools can’t handle some of its specifics.

Composable instead of View. Jetpack Compose constructs UI with Composables and doesn’t use Android Views. Composable is also a UI element. It has semantics that describes its attributes. All composables are combined in a single UI tree with semantics that describes its children.

Compose Layout doesn’t have IDs and tags. Instead, there’s the testTag attribute in semantics that allows you to add a unique identifier to a Composable.

Different testing tools. Espresso and UIAutomator can still test a Compose Layout — searching by text, resource, etc. However, they don’t have access to Composables’ semantics and can’t fully test them. Therefore, it’s recommended to use the Jetpack Compose testing library as it can access semantics and fully test Composables on the screen.

Compose tests are synchronized by default. Moreover, they don’t run in real time, but use a virtual clock so they can pass as fast as possible.

Use AndroidComposeTestRule or ComposeTestRule test rule.

Add testing dependencies to the build.gradle file:

def compose_version = '1.0.1'

Let’s assume we have a screen with a single button.

The layout for this screen:
fun MainScreen() {
contentAlignment = Alignment.Center,
modifier = Modifier
) {
onClick = {...},
modifier = Modifier.testTag("yourTestTag")
) {
Text(text = stringResource(

Now we should make sure it is displayed on the screen and then click on it. How is this done?

To test the screen we first need to open a test class in the androidTest folder.
Create a testRule with createAndroidTestRule. Pass the activity class that holds the UI:

class ExampleInstrumentedTest {
val composeTestRule = createAndroidTestRule(

Now write a test where the button is found by its testTag. Check that it is displayed and then click on it.

fun testButtonClick() {
val button = composeTestRule.onNode(hasTestTag("yourTestTag"), useUnmergedTree = true)

In this test we have:
composeTestRule – a TestRule to test UI created with Compose
onNode – a finder
hasTestTag – a Matcher
useUnmergedTree – a parameter that controls UI tree hierarchy representation
asssertExists – an assertion
performClick – an action

composeTestRule finds the UI element by its semantics attributes such as testTag, content description, or a custom property of a Composable. It has access the entire semantics tree of the UI that is on a screen.

Finders look for the Composable with a matching criterion and return a SemanticsNodeInteraction that holds the Composable and its children if there are any.

Some common finders:

  • onNode — looks for a single Composable that matches the searching criteria. Throws an exception if more than one matching Composable is found.
  • onAllNodes — looks for all nodes with a matching criterion. Returns a non-iterable SemanticsNodeInteractionCollection that holds found Composables and its possible children.
  • onNodeWithTag — looks for a single Composable with the specified testTag
  • onNodeWithText — looks for a single Composable with the specified text. A localized string can be searched by retrieving it with

A matcher specifies the criteria a finder uses to find the Composable. For example:

  • hasContentDescription — verifies that the Composable has specified content description.
  • hasTestTag — verifies that the Composable has the specified test tag.
  • isRoot — verifies that it is the root Composable.

There are also hierarchical matchers and selectors.

Hierarchical matchers verify the position of the Composable in the UI tree with methods like hasParent() or hasAnyChild().

Selectors can figure out Composables around and filter them.
For example, given the following tree:

|-Root composable

calling onSiblings() on ButtonTwo will return buttonOne and buttonThree Composables.

The full list of matchers is below.

Compose layout flattens its UI tree so some UI elements can be combined into a single Composable. For example, 2 texts can be merged into a single Text Composable. Thus, some semantics can be lost. In order to inspect an intact UI tree useUnmergedTree should be true .

They verify that the Composable meets a specific condition.

Some common assertions:

  • assertExists
  • assertIsEnabled
  • assertTextEquals
  • assertContentDescription

Using generic assert(), you can provide your matcher and verify that it is satisfied for this node.

Actions simulate user events on Composable such us:

  • performClick
  • performScroll
  • performTextInput

It also supports different kinds of gestures.

The full list of Finders, Matchers, Assertions, and Actions can be found in Jetpack Compose testing cheatsheet.

Jetpack Compose also allows testing only the layout itself instead of the entire app.
To do this use createComposeRule instead of createAndroidComposeRule.

val composeTestRule = createComposeRule()

And then set the layout Composable(MainScreen) right in the test:

fun testButtonClick() {
composeTestRule.setContent {
MyAppTheme {
val button = composeTestRule.onNode(hasTestTag("yourTestTag"), true)

It is even possible to create the UI right inside the test:

fun testButtonClick() {
composeTestRule.setContent {
Column {
onClick = {...},
modifier = Modifier.testTag("yourTestTag")
) {
Text(text = "Click")
val button = composeTestRule.onNode(hasTestTag("yourTestTag"), true)

Job Offers

Job Offers

    Android Build Engineer

    San Francisco, CA | Seattle, WA
    • Full Time
    apply now

    Android App Developer

    sipgate GmbH
    Düsseldorf, Remote
    • Full Time
    apply now

    Senior Mobile Systems SDK Engineer

    Sauce Labs
    • Full Time
    apply now
Load more listings



Leveling Up Your Tests

We all know about TDD and Unit Testing, and even screenshot testing, but sometimes we do not need to embrace a new paradigm to make our tests better. These are several techniques I have adopted…
Watch Video

Leveling Up Your Tests


Is Composable compatible with View?

  • Yes, they are interoperable. It is possible to add an Android View to Composable and vice versa.

Can I use Espresso and UIAutomator to test UI created with Jetpack Compose?

  • Yes. You can search on the UI by text or resource to find the elements and interact with them.

What’s the difference between createComposeRule and createAndroidComposeRule?

  • createAndroidComposeRule is an Android-specific TestRule as it holds a reference to the activity it runs.
    createComposeRule is crossplatform and has no ties to Android.

It took me a good deal of time to figure out what was the root cause of the issue. Turned out, UI tests for Compose cannot run properly if the tested activity launchMode is singleInstance


Removing this attribute will fix the issue. However, if it’s not an option, then there are a few other ways to fix it:

  1. Override/remove the attribute for UI test with a different manifest

When assembling an app, Gradle merges manifests that your app, dependencies, and modules may have.
You can override or remove completely the android:launchMode attribute by node markers.
By default, androidTest runs in the debug build type. So adding a proper node marker to Manifest in the/debug directory will override it for the app used by androidTest tests.

⚠️ However, it will also affect ordinary debug builds. Read further if it’s undesirable.

2. Create a separate build variant for Compose UI tests

Just making a separate build type solves the issue and also keeps UI tests available for multiple app flavors.
Don’t forget adding testBuildType “staging” // TODO Update this line

⚠️ Build type can’t be named compose as it is a reserved word.

In my case, the root cause of the issue was due to using the wrong scrolling functionality in LazyList.

I could only fix the issue using animateScrollToItem(index) instead of scrollToItem(index).

Testing with Compose Layout

Android Codelabs for Jetpack Compose Testing

Testing cheatsheet

Android Developers Backstage: Episode 171: Compose Testing



There are multiple ways of doing the same thing. You choose which way to…

1 Comment. Leave new

Leave a Reply

Your email address will not be published.

Fill out this field
Fill out this field
Please enter a valid email address.