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
- ViewMatchers — Find views by ID, text, content description, or position.
- ViewActions — Perform interactions: click, type, scroll, swipe.
- 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?
2. How do you click an item at position 3 in a RecyclerView with Espresso?
3. What replaces the deprecated ActivityTestRule?
4. What does closeSoftKeyboard() do and why is it important?
Flashcards
Question
What are the three pillars of Espresso?
Click to reveal answer
Answer
ViewMatchers (find views), ViewActions (interact with views), ViewAssertions (verify view state).
Question
How do you handle RecyclerView items in Espresso?
Click to reveal answer
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?
Click to reveal answer
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?
Click to reveal answer
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