Compose & Espresso UI Testing Quiz
TESTING › UI
In Compose UI testing, what does a finder like onNodeWithText() query against?
- The raw Composable call tree built from your UI code
- The semantics tree used for accessibility and testing
- The Android View hierarchy searched through findViewById
- A bitmap snapshot of the screen after it is drawn
Answer: The semantics tree used for accessibility and testing
Compose tests operate on the semantics tree, the same structure that powers accessibility, rather than the View hierarchy or rendered pixels.
Which rule should you use to test a Composable and also get a reference to the hosting Activity?
- createComposeRule() for hosting composables without an Activity
- ActivityScenarioRule alone, since it does not expose Compose UI
- createAndroidComposeRule<MainActivity>() for the hosting Activity
- createEmptyComposeRule() when you do not need any Activity reference at all
Answer: createAndroidComposeRule<MainActivity>() for the hosting Activity
createAndroidComposeRule<T>() launches the specified Activity and exposes it, whereas createComposeRule() hosts content without a particular Activity.
What is the correct order of the Espresso interaction pattern?
- onView(matcher).check(assertion).perform(action)
- perform(action).onView(matcher).check(assertion)
- onView(matcher).perform(action).check(assertion)
- check(assertion).onView(matcher).perform(action)
Answer: onView(matcher).perform(action).check(assertion)
Espresso uses onView(viewMatcher).perform(viewAction).check(viewAssertion): locate the view, act on it, then verify.
A node is in the semantics tree but scrolled off screen. Which assertion passes?
- assertIsDisplayed() passes but assertExists() fails
- assertExists() passes but assertIsDisplayed() fails
- Both pass, since the node is present and counts as displayed
- Both fail, because an off-screen node is absent from the tree
Answer: assertExists() passes but assertIsDisplayed() fails
assertExists() only checks presence in the semantics tree; assertIsDisplayed() additionally requires the node to be visible, which an off-screen node is not.
Why does Espresso usually not require manual Thread.sleep() calls between actions?
- It runs every test on a real device that is always fast enough
- It waits for the message queue, AsyncTasks, and idling resources
- It disables all animations globally, so nothing is ever left pending
- It re-runs each failing assertion up to ten times automatically
Answer: It waits for the message queue, AsyncTasks, and idling resources
Before each onView interaction, Espresso waits for the UI thread message queue, AsyncTasks, and registered idling resources to be idle, removing the need for manual sleeps.
You need to match a child Text inside a Button that Compose has merged into the parent's semantics. What do you do?
- Pass useUnmergedTree = true to the finder
- Call assertExists() twice to force a refresh
- Wrap the Text in its own Activity
- Remove the testTag from the Button
Answer: Pass useUnmergedTree = true to the finder
Merged semantics hide child nodes; querying with useUnmergedTree = true exposes the individual child so it can be matched.
Which Espresso API should you use to interact with an item in a Spinner or ListView?
- onView() with withText() to target the visible item
- onData() with a matcher on the adapter's backing data
- performScrollTo() on the root view before matching text
- onNodeWithTag() from the Compose test API for the list
Answer: onData() with a matcher on the adapter's backing data
AdapterView-backed widgets recycle off-screen items, so Espresso.onData() matches against the adapter's data and scrolls to it, unlike onView().
Which test runner must be declared as testInstrumentationRunner in build.gradle for AndroidX instrumented UI tests to run on a device?
- org.junit.runners.JUnit4
- androidx.test.runner.AndroidJUnitRunner
- android.test.InstrumentationTestRunner
- androidx.benchmark.junit4.AndroidBenchmarkRunner
Answer: androidx.test.runner.AndroidJUnitRunner
AndroidX instrumented tests use androidx.test.runner.AndroidJUnitRunner; the old android.test.InstrumentationTestRunner is deprecated and JUnit4's runner does not run on-device.
An Espresso test reliably fails because of an enabled system animation. What is the standard fix?
- Add a Thread.sleep(2000) call before each and every assertion
- Disable the window, transition, and animator animation scales
- Wrap every Espresso action inside a runOnUiThread block
- Switch all the test assertions over to onData() matchers
Answer: Disable the window, transition, and animator animation scales
Espresso cannot synchronize with animations, so the convention is to turn off the three system animation scales (or set testOptions.animationsDisabled true), not to add sleeps.
In Compose testing, what is the purpose of composeTestRule.waitUntil { ... }?
- It forces an immediate recomposition of the entire Compose tree
- It polls a condition, driving the clock, until true or timeout
- It permanently pauses Compose's automatic synchronization
- It captures a screenshot once the screen becomes idle
Answer: It polls a condition, driving the clock, until true or timeout
waitUntil repeatedly evaluates the supplied condition (driving the test clock) and returns when it becomes true, throwing if the timeout elapses first; it is the way to wait on conditions Compose's auto-sync cannot see.
You want a single Espresso assertion that no Toast or other view anywhere on screen displays a given error text. Which approach is correct?
- onView(withText(errorText)).check(matches(isDisplayed()))
- onView(withText(errorText)).check(doesNotExist())
- onView(withId(R.id.error)).perform(clearText())
- assertThat(errorText).isNull()
Answer: onView(withText(errorText)).check(doesNotExist())
ViewAssertions.doesNotExist() passes only when the matcher resolves to no view in the hierarchy, which is how you assert absence; matches(isDisplayed()) would require the view to be present and visible.
Which dependency configuration correctly places the Compose UI test rule so it only ships in instrumented test builds?
- implementation("androidx.compose.ui:ui-test-junit4") in main app code
- testImplementation("androidx.compose.ui:ui-test-junit4") for local JVM tests
- androidTestImplementation("androidx.compose.ui:ui-test-junit4")
- debugImplementation("androidx.compose.ui:ui-test-junit4") in debug builds only
Answer: androidTestImplementation("androidx.compose.ui:ui-test-junit4")
Compose UI tests run as instrumented tests, so ui-test-junit4 goes under androidTestImplementation; the debugImplementation slot is reserved for ui-test-manifest, not the test rule itself.
How do you reliably interact with an item inside a Compose LazyColumn whose item is not currently composed because it is scrolled off screen?
- It's already in the semantics tree, so performClick() just works
- Use onNodeWithTag on the list, then performScrollToNode
- Call assertExists(), which forces the off-screen item to compose
- Switch the list to onData(), like an Espresso AdapterView list
Answer: Use onNodeWithTag on the list, then performScrollToNode
Lazy lists only compose visible items, so off-screen nodes are absent from the semantics tree; you scroll the list node with performScrollToNode/performScrollToIndex to compose the target before acting on it.
What does setting composeTestRule.mainClock.autoAdvance = false enable you to do?
- Run the test against the real device clock for more accurate timing
- Step the clock via advanceTimeBy to inspect animation frames
- Disable all semantics-tree merging for the whole test
- Skip waiting on idling resources entirely during the test
Answer: Step the clock via advanceTimeBy to inspect animation frames
Turning off autoAdvance hands you manual control of the test clock so you can step time with advanceTimeBy and inspect a UI mid-animation, rather than letting Compose jump straight to the idle end state.