Skip to content
intermediate Phase 10 · Testing

Espresso UI Tests

Write instrumented UI tests with Espresso: matchers, actions, and view assertions.

50m
3 problems
Topic Progress 0%

Espresso Fundamentals

Espresso Fundamentals

Espresso is Android's standard UI testing framework. It runs on a device or emulator, interacts with real views, and waits for the UI to idle before performing actions.

Basic Structure

@RunWith(AndroidJUnit4::class)
class LoginActivityTest {

    @get:Rule
    val activityRule = ActivityScenarioRule(LoginActivity::class.java)

    @Test
    fun loginWithValidCredentials_navigatesToHome() {
        // Type into email field
        onView(withId(R.id.email_input))
            .perform(typeText("test@example.com"))

        // Type into password field
        onView(withId(R.id.password_input))
            .perform(typeText("password123"))

        // Click login button
        onView(withId(R.id.login_button))
            .perform(click())

        // Verify home screen appears
        onView(withId(R.id.home_welcome_text))
            .check(matches(isDisplayed()))
    }
}

The Three Pillars

  1. ViewMatchers — Find views by ID, text, content description, or position.
  2. ViewActions — Perform interactions: click, type, scroll, swipe.
  3. ViewAssertions — Verify view state: displayed, enabled, text content.

ActivityScenarioRule

ActivityScenarioRule launches the activity before each test and closes it after. It replaces the deprecated ActivityTestRule.

@get:Rule
val activityRule = ActivityScenarioRule(DetailActivity::class.java)

You can pass an Intent to launch with specific extras:

val intent = Intent(ApplicationProvider.getApplicationContext(), DetailActivity::class.java).apply {
    putExtra("product_id", "42")
}
@get:Rule
val activityRule = ActivityScenarioRule(intent)

Espresso Idling Resources

Espresso waits for the UI thread to be idle before performing actions. For background work (network calls, database writes), you must register an idling resource:

@Before
fun setUp() {
    Espresso.registerIdlingResources(countingIdlingResource)
}

@After
fun tearDown() {
    Espresso.unregisterIdlingResources(countingIdlingResource)
}

Without this, Espresso may time out or interact with a partially loaded screen.

Matchers and Actions

Matchers

Matchers find views on screen. Espresso provides several built-in matchers:

// By resource ID
onView(withId(R.id.submit_button))

// By text
onView(withText("Submit"))

// By partial text
onView(withSubstring("Submit"))

// By content description (accessibility)
onView(withContentDescription("Submit button"))

// By view type
onView(isAssignableFrom(EditText::class.java))

// Compound matcher
onView(allOf(
    withId(R.id.button),
    withText("OK"),
    isDisplayed()
))

// Either matcher
onView(anyOf(
    withText("OK"),
    withText("Accept")
))

RecyclerView Matchers

RecyclerView requires special handling because items are not all present at once:

// Scroll to a position
onView(withId(R.id.recycler_view))
    .perform(scrollToPosition<ViewHolder>(5))

// Scroll to a view with specific text
onView(withId(R.id.recycler_view))
    .perform(scrollTo<ViewHolder>(hasDescendant(withText("Target Item"))))

// Click an item at position
onView(withId(R.id.recycler_view))
    .perform(actionOnItemAtPosition<ViewHolder>(0, click()))

// Click item matching a view matcher
onView(withId(R.id.recycler_view))
    .perform(actionOnHolderItem(
        hasDescendant(withText("Target")),
        click()
    ))

Actions

Actions interact with views:

// Click
onView(withId(R.id.button)).perform(click())

// Long click
onView(withId(R.id.button)).perform(longClick())

// Type text (replaces existing text)
onView(withId(R.id.input)).perform(typeText("hello"))

// Type text appended
onView(withId(R.id.input)).perform(replaceText("hello"))

// Clear and type
onView(withId(R.id.input)).perform(clearText(), typeText("hello"))

// Scroll
onView(withId(R.id.scroll_view)).perform(swipeUp())

// Close soft keyboard
onView(withId(R.id.input)).perform(closeSoftKeyboard())

// Press back
onView(withId(R.id.button)).perform(pressBack())

// Custom action
fun clickChildView(id: Int): ViewAction = object : ViewAction {
    override fun getConstraints() = isAssignableFrom(View::class.java)
    override fun getDescription() = "Click child view with id $id"
    override fun perform(uiController: UiController, view: View) {
        view.findViewById<View>(id).performClick()
    }
}

ViewAssertions

Assertions verify view state:

// View is displayed
onView(withId(R.id.text)).check(matches(isDisplayed()))

// View is not displayed
onView(withId(R.id.text)).check(matches(not(isDisplayed())))

// View has specific text
onView(withId(R.id.text)).check(matches(withText("Hello")))

// View has specific error
onView(withId(R.id.input)).check(matches(hasErrorText("Required")))

// View is enabled/disabled
onView(withId(R.id.button)).check(matches(isEnabled()))

// View is checked (for CheckBox, Switch)
onView(withId(R.id.checkbox)).check(matches(isChecked()))

// Count items in RecyclerView
onView(withId(R.id.recycler_view))
    .check(matches(hasChildCount(10)))

Custom Matchers

Write reusable matchers:

fun withDrawable(@DrawableRes id: Int): Matcher<View> {
    return object : BoundedMatcher<View, ImageView>(ImageView::class.java) {
        override fun describeTo(description: Description) {
            description.appendText("with drawable id $id")
        }

        override fun matchesSafely(imageView: ImageView): Boolean {
            val drawable = ContextCompat.getDrawable(imageView.context, id)
            return imageView.drawable?.constantState == drawable?.constantState
        }
    }
}

Quiz

1. What must you register for Espresso to work with async network calls?

Question 1 options

2. How do you click an item at position 3 in a RecyclerView with Espresso?

Question 2 options

3. What replaces the deprecated ActivityTestRule?

Question 3 options

4. What does closeSoftKeyboard() do and why is it important?

Question 4 options

Flashcards

Question

What are the three pillars of Espresso?

Answer

ViewMatchers (find views), ViewActions (interact with views), ViewAssertions (verify view state).

Question

How do you handle RecyclerView items in Espresso?

Answer

Use actionOnItemAtPosition to click, scrollToPosition to scroll, and actionOnHolderItem to interact with items matching a view matcher.

Question

What is an IdlingResource in Espresso?

Answer

A callback that tells Espresso when async work (network, database) is complete so it knows when it is safe to perform actions.

Question

How do you launch an activity with extras in Espresso?

Answer

Create an Intent with putExtra and pass it to ActivityScenarioRule(intent).

Revision Notes

Key Takeaways

  • 1. Espresso waits for UI idle before performing actions — register IdlingResources for async work
  • 2. Use actionOnItemAtPosition for RecyclerView item interactions
  • 3. ActivityScenarioRule replaces the deprecated ActivityTestRule
  • 4. Always closeSoftKeyboard after typing to avoid view obstruction
  • 5. Write custom matchers for reusable assertions across tests

Interview Tips

  • Explain how Espresso synchronizes with the UI thread using idling resources
  • Discuss how you test RecyclerView scrolling and item clicks
  • Know the difference between ActivityScenarioRule and the old ActivityTestRule
  • Be ready to describe a custom matcher you might write

Cheat Sheet

Espresso Cheat Sheet

Matchers:

  • withId(R.id.view)
  • withText("text")
  • withSubstring("partial")
  • withContentDescription("desc")
  • isDisplayed(), isEnabled()
  • allOf(), anyOf()

Actions:

  • click(), longClick()
  • typeText("text"), replaceText("text")
  • clearText(), closeSoftKeyboard()
  • swipeUp(), scrollTo()
  • pressBack()

RecyclerView:

  • scrollToPosition(n)
  • actionOnItemAtPosition(n, action)
  • actionOnHolderItem(matcher, action)

Assertions:

  • check(matches(isDisplayed()))
  • check(matches(withText("text")))
  • check(matches(hasChildCount(n)))

Setup:

  • ActivityScenarioRule for launching activities
  • registerIdlingResources for async work