Jetpack Compose UI Testing: Setup and First Test

Quick answer: Put Compose UI tests in src/androidTest, add ui-test-junit4, and use a Compose test rule to mount or launch UI. Then find an accessible semantic node, perform the same action a user would, and assert the visible result. Start with a small composable in isolation; use an activity rule only when the test needs the real activity or Android resources.

Compose tests are instrumented tests: they run on an emulator or physical device, not as ordinary JVM unit tests. Their purpose is to prove an observable contract—what a person can find, do, and see—not the implementation details of a composable. Android’s Compose testing overview calls out the three building blocks: semantics, test APIs, and synchronization.

Add the test dependencies

Add the test rule to the module that contains the Compose UI. If the module already uses the Compose BOM, keep using that existing BOM declaration so Compose artifacts stay aligned.

dependencies {
    androidTestImplementation(
        platform("androidx.compose:compose-bom:<bom-version>"),
    )
    androidTestImplementation("androidx.compose.ui:ui-test-junit4")

    // Provides an empty activity for tests created with createComposeRule().
    debugImplementation("androidx.compose.ui:ui-test-manifest")
}

ui-test-junit4 supplies ComposeTestRule and the APIs used to find nodes, send actions, and make assertions. ui-test-manifest is needed for createComposeRule() because that rule hosts your supplied content in a test activity; it is not needed merely to launch your own activity with createAndroidComposeRule<YourActivity>(). The official setup instructions document that distinction.

Place the test under the androidTest source set, for example:

app/
  src/
    androidTest/
      java/com/example/app/ConfirmationPanelTest.kt

Choose a rule for the scope of the test

Use the smallest environment that proves the behavior you care about.

NeedRuleWhat it gives you
Test one composable or screen with supplied state and callbackscreateComposeRule()A host where the test calls setContent.
Test a real activity, resources, intents, or Android integrationcreateAndroidComposeRule<MainActivity>()The launched activity plus Compose test APIs.

For most first tests, start with createComposeRule(). It keeps the setup short and makes failures specific to the component. The common testing patterns guide recommends testing composables in isolation as well as retaining larger UI tests where they add value.

Make the composable expose a real user outcome

Here is a deliberately small UI contract: tapping Confirm reveals a success message. The state is local only to keep the example focused; production screens can receive state and event callbacks from a ViewModel or parent.

@Composable
fun ConfirmationPanel() {
    var confirmed by rememberSaveable { mutableStateOf(false) }

    Column {
        Button(onClick = { confirmed = true }) {
            Text("Confirm")
        }

        if (confirmed) {
            Text("Confirmed")
        }
    }
}

The labels are not test-only hooks. They are visible, meaningful UI that a user—and assistive technology—can understand. That makes them a strong first test seam.

Write the first test

Create a test class in src/androidTest/java. The rule mounts the content, waits for Compose to settle around each interaction, and exposes semantic finders such as onNodeWithText.

import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import org.junit.Rule
import org.junit.Test

class ConfirmationPanelTest {
    @get:Rule
    val composeTestRule = createComposeRule()

    @Test
    fun confirm_showsConfirmationMessage() {
        composeTestRule.setContent {
            ConfirmationPanel()
        }

        composeTestRule.onNodeWithText("Confirm").performClick()

        composeTestRule.onNodeWithText("Confirmed").assertIsDisplayed()
    }
}

Read the test as a tiny user story: show the panel, tap the confirmation control, then see confirmation. The official testing APIs reference explains that finders select nodes from the semantics tree, actions inject input, and assertions verify the resulting UI.

Prefer semantics over implementation details

Compose does not turn every composable into a View. Tests interact with the semantics tree: the accessibility and testing meaning emitted by the UI. Prefer a matcher that describes the interface itself:

composeTestRule.onNodeWithText("Save").performClick()
composeTestRule.onNodeWithContentDescription("Close dialog").performClick()

Use a test tag when no durable user-facing semantic is available, such as a repeated visual container or an icon whose content description is intentionally absent:

IconButton(
    onClick = onRetry,
    modifier = Modifier.testTag("retry-button"),
) {
    Icon(Icons.Default.Refresh, contentDescription = null)
}

composeTestRule.onNodeWithTag("retry-button").performClick()

Do not add text, content descriptions, or tags only to appease a test if they make the product less clear. Instead, expose correct semantics for real users, then select those semantics in the test. The next article, Find Nodes and Assert UI with Compose Test APIs, goes deeper into matchers, merged semantics, and assertions.

When to use an activity rule

Switch to an activity-backed rule when the behavior depends on the app activity: an intent, a resource, an Activity Result API, or the actual app navigation setup.

import androidx.compose.ui.test.junit4.createAndroidComposeRule
import org.junit.Rule

class MainActivityTest {
    @get:Rule
    val composeTestRule = createAndroidComposeRule<MainActivity>()
}

Let the activity own its normal startup content. If it already calls setContent, do not call composeTestRule.setContent again in the test. For navigation-specific coverage, mount the real graph with a test controller as shown in Testing Navigation Compose: NavHost, Actions, and Back Stack.

Run the test on a device

Start an emulator or connect a device, then run the module’s instrumented tests. In a typical single-app project:

./gradlew connectedDebugAndroidTest

Android Studio can run an individual androidTest class or method as well. Start with one fast test like the example, then add tests around behavior that is costly to break: form submission, error and retry states, destructive actions, and critical navigation paths.

Avoid these first-test traps

Testing private state instead of behavior

Avoid asserting a local Boolean, calling an internal callback directly, or checking a composable’s implementation structure. Click the semantic control and assert what changed on screen. This stays useful when the component is refactored.

Starting with the entire app

A full app test is valuable, but it is a poor first failure to debug. Mount a single screen or component first, supply deterministic data, and keep network, clocks, and repositories outside the test. Broaden the scope when the integration boundary itself matters.

Sleeping after every action

Compose test APIs synchronize with the UI before actions and assertions in normal cases. A fixed Thread.sleep() makes tests slower and still flaky. For controlled asynchronous work or animations, use Compose’s testing synchronization and clock tools; those are covered in Test Compose Animations and Time Deterministically.

Treating test tags as the default selector

Tags are useful, but text, roles, state, and content descriptions exercise the same semantics that accessibility services use. Begin with the most meaningful semantic matcher and add a tag only when it represents a stable, non-user-facing identity.

A practical first-test checklist

  • Add ui-test-junit4 to androidTestImplementation and ui-test-manifest to debugImplementation when using createComposeRule().
  • Put the test in src/androidTest, then run it on an emulator or device.
  • Use createComposeRule() for isolated Compose content; use createAndroidComposeRule when the real activity is in scope.
  • Find a semantic node, perform an action, and assert a visible outcome.
  • Keep the first test small, deterministic, and about one behavior.

That is enough foundation to build a useful UI test suite. Next, learn how to choose nodes precisely and make stronger assertions without coupling tests to layout structure.