UI Testing with Espresso
Write automated UI tests with Espresso. Find views with onView(), interact with perform(), assert state with check(), and test RecyclerView interactions.
UI Testing with Espresso is a free Android Academy lesson on CoddyKit — lesson 3 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Android Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
What Is Espresso?
Espresso is Android's official UI testing framework. It lets you write tests that interact with your app's UI — tapping buttons, typing text, scrolling lists — and assert what's displayed on screen.
Espresso runs on a device or emulator (instrumented test), and it automatically synchronizes with the UI thread — no flaky sleep() calls needed.
Setup
Espresso is included in the Android testing toolkit. Add these dependencies to app/build.gradle:
// app/build.gradle:
android {
defaultConfig {
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
}
dependencies {
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.5.1' // RecyclerView
}ActivityScenarioRule
Use ActivityScenarioRule to launch an Activity before each test and close it after:
@RunWith(AndroidJUnit4::class)
class LoginActivityTest {
@get:Rule
val activityRule = ActivityScenarioRule(LoginActivity::class.java)
@Test
fun `login button is displayed`() {
onView(withId(R.id.btnLogin))
.check(matches(isDisplayed()))
}
}onView() and ViewMatchers
onView(matcher) finds a view in the current hierarchy. Combine matchers for precision:
// Match by ID:
onView(withId(R.id.tvTitle))
// Match by text:
onView(withText("Submit"))
// Match by content description (for accessibility):
onView(withContentDescription("Close dialog"))
// Combine matchers:
onView(allOf(withId(R.id.btnSave), isEnabled()))
// Inside a specific parent:
onView(withId(R.id.btnSave)).inRoot(isDialog())ViewActions — Interacting with Views
After finding a view, perform an action with .perform():
// Tap a button:
onView(withId(R.id.btnLogin)).perform(click())
// Type into an EditText:
onView(withId(R.id.etEmail))
.perform(typeText("alice@example.com"), closeSoftKeyboard())
// Clear text and retype:
onView(withId(R.id.etPassword))
.perform(clearText(), typeText("secret123"), closeSoftKeyboard())
// Scroll to a view:
onView(withId(R.id.btnSubmit)).perform(scrollTo(), click())
// Long press:
onView(withId(R.id.ivImage)).perform(longClick())ViewAssertions — Verifying State
Check what's shown on screen with .check():
// View is displayed:
onView(withId(R.id.tvError)).check(matches(isDisplayed()))
// View has specific text:
onView(withId(R.id.tvTitle)).check(matches(withText("Welcome!")))
// View is not displayed (GONE or INVISIBLE):
onView(withId(R.id.progressBar)).check(matches(not(isDisplayed())))
// View is enabled:
onView(withId(R.id.btnSave)).check(matches(isEnabled()))
// View does not exist:
onView(withId(R.id.tvError)).check(doesNotExist())Complete Login Test
A full test that types credentials and taps login:
@Test
fun `successful login shows home screen`() {
// Type email
onView(withId(R.id.etEmail))
.perform(typeText("alice@example.com"), closeSoftKeyboard())
// Type password
onView(withId(R.id.etPassword))
.perform(typeText("password123"), closeSoftKeyboard())
// Tap login button
onView(withId(R.id.btnLogin)).perform(click())
// Assert welcome message is shown
onView(withId(R.id.tvWelcome))
.check(matches(withText("Welcome, Alice!")))
}Testing RecyclerView
Use RecyclerViewActions from espresso-contrib to interact with list items:
import androidx.test.espresso.contrib.RecyclerViewActions
@Test
fun `clicking item opens detail screen`() {
// Scroll to position 5 and click:
onView(withId(R.id.recyclerView))
.perform(RecyclerViewActions.actionOnItemAtPosition<RecyclerView.ViewHolder>(
5, click()
))
// Assert detail screen is shown:
onView(withId(R.id.tvDetailTitle)).check(matches(isDisplayed()))
}Testing Input Validation
Verify that error messages appear when input is invalid:
@Test
fun `empty email shows error message`() {
// Leave email blank, tap login
onView(withId(R.id.btnLogin)).perform(click())
// Assert error is shown
onView(withId(R.id.tilEmail))
.check(matches(hasDescendant(withText("Email is required"))))
}
@Test
fun `short password shows error`() {
onView(withId(R.id.etPassword))
.perform(typeText("123"), closeSoftKeyboard())
onView(withId(R.id.btnLogin)).perform(click())
onView(withId(R.id.tilPassword))
.check(matches(hasDescendant(withText("Minimum 6 characters"))))
}Idling Resources
Espresso automatically waits for the UI thread to be idle, but not for network calls or coroutines. Use IdlingResource to tell Espresso to wait for your async work:
// CountingIdlingResource: increment before async work, decrement when done
val idlingResource = CountingIdlingResource("NetworkCall")
// In your ViewModel/Repository:
idlingResource.increment()
viewModelScope.launch {
loadData()
idlingResource.decrement()
}
// In test setUp:
@Before
fun setUp() {
IdlingRegistry.getInstance().register(idlingResource)
}
@After
fun tearDown() {
IdlingRegistry.getInstance().unregister(idlingResource)
}Running Espresso Tests
Run instrumented tests on a connected device or emulator:
- In Android Studio: right-click a test class → Run
- Via terminal:
./gradlew connectedAndroidTest - Results appear in
app/build/reports/androidTests/
Use Android Emulator API 28+ for stable, fast test runs. Disable animations in developer settings to prevent flakiness.
Quick Check
What is the correct Espresso pattern for finding a view and checking its text?
Recap: UI Testing with Espresso
Automated UI testing catches regressions before users do:
onView(matcher)— find a view.perform(action)— tap, type, scroll.check(matches(...))— assert visibility, text, stateRecyclerViewActionsfor list interactionsIdlingResourcefor async operations- Disable system animations to prevent flaky tests
Next: debug and profile your app like a pro.
Frequently asked questions
Is the “UI Testing with Espresso” lesson free?
Yes — the full text of “UI Testing with Espresso” is free to read here on the web, and the Android Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Android Academy course, upgrade to CoddyKit PRO.
What will I learn in “UI Testing with Espresso”?
Write automated UI tests with Espresso. Find views with onView(), interact with perform(), assert state with check(), and test RecyclerView interactions. You practise Android Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Android Academy?
No prior experience is required. Android Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “UI Testing with Espresso” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Android Academy lesson?
Yes. Every Android Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Unit Testing with JUnit
- Mocking with Mockito
- UI Testing with Espresso
- Debugging & Profiling