Creating UI Tests with Espresso or Compose Testing
Course: Android App Development
Topic: Creating UI Tests with Espresso or Compose Testing
Course Link: Android App Development Course
Demo Link: Register for a Course Demo
Website: JustAcademy
1. Introduction to Android UI Testing
User Interface (UI) testing is the process of verifying that an Android application displays the correct information and responds properly to user interactions. It helps developers ensure that buttons, text fields, menus, navigation components, dialogs, and other interface elements behave as expected.
Android applications can be developed using traditional Android Views, XML layouts, or Jetpack Compose. The appropriate UI testing framework depends on the technology used to build the interface.
Espresso is commonly used to test applications built with Android Views. Compose UI Testing provides APIs specifically designed for testing Jetpack Compose interfaces. Both frameworks support finding UI elements, performing actions, and verifying the resulting behavior.
Why Is UI Testing Important?
- Verifies that application screens display the expected content.
- Checks whether buttons and other controls respond correctly.
- Validates user workflows such as login, registration, and checkout.
- Detects UI regressions after code changes.
- Verifies input validation and error messages.
- Improves application reliability and user experience.
- Reduces repetitive manual testing.
- Supports automated testing in continuous integration pipelines.
2. Learning Objectives
After completing this topic, learners should be able to:
- Understand the purpose of Android UI testing.
- Differentiate Espresso testing from Compose UI testing.
- Configure the required testing dependencies.
- Create instrumented UI test classes.
- Locate UI elements using matchers and semantics.
- Simulate clicks, text entry, scrolling, and other interactions.
- Verify UI elements using assertions.
- Test form validation, navigation, and error handling.
- Understand UI synchronization and test stability.
- Run UI tests on Android emulators and physical devices.
3. Understanding Espresso
Espresso is an Android UI testing framework provided through AndroidX Test. It allows developers to interact with Views inside an application and verify their behavior.
Espresso tests generally follow three fundamental steps:
- Find: Locate the required UI element.
- Act: Perform an action, such as clicking a button or entering text.
- Assert: Verify the expected result.
Example of the Espresso Testing Process
Find the Login button
|
v
Enter username and password
|
v
Tap the Login button
|
v
Verify that the Dashboard is displayed
Important Espresso Features
- View matchers: Find views by resource ID, text, or other properties.
- View actions: Perform clicks, text entry, scrolling, and other interactions.
- View assertions: Check whether a view is displayed, enabled, or contains expected text.
- Automatic synchronization: Waits for supported UI work to become idle before performing actions or assertions.
- Integration with Android Studio: Supports running and debugging instrumented tests.
- Custom synchronization: Supports idling resources for certain asynchronous operations.
4. Understanding Jetpack Compose UI Testing
Jetpack Compose is Android's modern declarative UI toolkit. Instead of defining interfaces primarily through XML layouts, developers build screens using Kotlin functions called composables.
Compose UI Testing provides APIs for locating composables through their semantics, performing interactions, and checking the resulting UI state.
Important Compose Testing Concepts
- Semantics: Information describing the meaning and behavior of UI elements for accessibility and testing.
- ComposeTestRule: A test rule used to set up and interact with Compose content.
- Node finders: APIs such as
onNodeWithText() and onNodeWithTag() for locating UI elements.
- Actions: Methods such as
performClick() and performTextInput().
- Assertions: Methods such as
assertIsDisplayed() and assertTextEquals().
- Synchronization: Coordinates test operations with UI updates, recompositions, and supported animations.
Compose testing can run against a complete application screen or a smaller composable in isolation. Isolated tests are useful for checking individual components, while application-level tests verify complete user workflows.
5. Espresso vs. Compose UI Testing
| Feature |
Espresso |
Compose UI Testing |
| Primary target |
Traditional Android Views |
Jetpack Compose interfaces |
| Element identification |
View matchers, resource IDs, and text |
Compose semantics, text, and test tags |
| Finding elements |
onView() |
onNode() and related finders |
| Click interaction |
perform(click()) |
performClick() |
| Text entry |
typeText() |
performTextInput() |
| Assertions |
Hamcrest matchers and Espresso assertions |
Compose testing assertion APIs |
| Synchronization |
Coordinates supported UI work and registered idling resources |
Coordinates Compose UI actions, recompositions, and supported animations |
| Best use |
Applications using Android Views |
Applications using Jetpack Compose |
Important: Choose the framework according to the interface being tested. An application containing both Views and Compose can use both testing approaches where appropriate.
6. Setting Up Espresso Testing
Espresso tests are generally stored in the src/androidTest directory and run on a real Android device or emulator.
Step 1: Configure the Instrumentation Runner
In the app module's Gradle configuration, configure the Android test runner:
android {
defaultConfig {
testInstrumentationRunner =
"androidx.test.runner.AndroidJUnitRunner"
}
}
Use the syntax appropriate to your project, such as Kotlin DSL or Groovy. The example above illustrates the setting rather than a complete build file.
Step 2: Add Testing Dependencies
dependencies {
androidTestImplementation(
"androidx.test.ext:junit:<compatible-version>"
)
androidTestImplementation(
"androidx.test:runner:<compatible-version>"
)
androidTestImplementation(
"androidx.test.espresso:espresso-core:<compatible-version>"
)
}
Replace the version placeholders with compatible versions from the project's dependency configuration and official AndroidX release information.
Step 3: Create the Test Directory
app/
└── src/
├── main/
│ ├── java/
│ └── res/
└── androidTest/
└── java/
└── com/example/app/
└── LoginUiTest.kt
Step 4: Prepare the Test Environment
- Build and install the application successfully.
- Configure an emulator or connect a compatible Android device.
- Ensure the required Activity can launch.
- Prepare predictable test data and test accounts.
- Disable or control animations if they interfere with test execution.
7. Writing Your First Espresso UI Test
Suppose an Android application contains a greeting screen with an input field, a button, and a text label. The test should enter a name, tap the button, and verify the greeting.
Example XML Layout
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<EditText
android:id="@+id/nameField"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Enter your name" />
<Button
android:id="@+id/greetButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Greet" />
<TextView
android:id="@+id/greetingText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="" />
</LinearLayout>
Example Espresso Test
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.closeSoftKeyboard
import androidx.test.espresso.action.ViewActions.typeText
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class GreetingUiTest {
@Test
fun enteringNameDisplaysGreeting() {
onView(withId(R.id.nameField))
.perform(typeText("Asha"), closeSoftKeyboard())
onView(withId(R.id.greetButton))
.perform(click())
onView(withId(R.id.greetingText))
.check(matches(isDisplayed()))
onView(withId(R.id.greetingText))
.check(matches(withText("Hello, Asha!")))
}
}
This test assumes that the application contains an Activity that launches the example layout and updates the greeting text when the button is clicked. The test must be adapted to the actual application implementation.
Understanding the Test
onView() locates a View in the current UI hierarchy.
withId() identifies a view by its resource ID.
typeText() enters text into the input field.
closeSoftKeyboard() closes the software keyboard when appropriate.
perform(click()) simulates a click.
matches(isDisplayed()) checks whether the element is displayed.
matches(withText()) verifies the expected text.
8. Testing Buttons, Text Fields, and Other Views with Espresso
Espresso provides different actions and matchers for testing common UI elements.
| Task |
Example API |
Purpose |
| Find a view by ID |
withId(R.id.saveButton) |
Locate a specific view. |
| Find a view by text |
withText("Save") |
Locate a view with matching text. |
| Tap a button |
perform(click()) |
Simulate a click. |
| Enter text |
perform(typeText("Hello")) |
Type into an editable field. |
| Clear text |
perform(clearText()) |
Remove existing input. |
| Check visibility |
matches(isDisplayed()) |
Verify that a view is displayed. |
| Check enabled state |
matches(isEnabled()) |
Verify that an interaction is enabled. |
| Scroll to a view |
perform(scrollTo()) |
Scroll a supported scrollable layout to a view. |
Example: Testing a Save Button
onView(withId(R.id.saveButton))
.check(matches(isDisplayed()))
.check(matches(isEnabled()))
.perform(click())
onView(withText("Saved successfully"))
.check(matches(isDisplayed()))
This example assumes that tapping the Save button displays a confirmation message. If saving is asynchronous, ensure the test correctly synchronizes with the operation.
9. Testing Form Validation with Espresso
Forms commonly require UI tests because incorrect validation can prevent users from completing important tasks.
Consider a registration form with an email field, password field, and Submit button. Important test scenarios include:
- Submitting an empty form.
- Entering an invalid email address.
- Entering a password that does not meet the requirements.
- Entering valid data.
- Correcting invalid data after an error appears.
Example: Empty Email Validation
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.espresso.Espresso.onView
import org.junit.Test
@Test
fun emptyEmailDisplaysValidationMessage() {
onView(withId(R.id.submitButton))
.perform(click())
onView(withId(R.id.emailError))
.check(matches(withText("Email is required")))
}
This test assumes the application displays the validation message in a view with ID emailError. The expected text and view ID must match the actual layout and requirements.
10. Setting Up Compose UI Testing
Compose UI tests use APIs provided by the Compose testing libraries. A test can render a composable directly or launch an Activity that hosts the application's Compose interface.
Step 1: Add Dependencies
dependencies {
androidTestImplementation(
"androidx.compose.ui:ui-test-junit4:$compose_version"
)
debugImplementation(
"androidx.compose.ui:ui-test-manifest:$compose_version"
)
}
Use a Compose version compatible with the project. The manifest dependency is typically useful when using createComposeRule() for tests that provide their own Compose content. Projects using an existing Activity can instead use an appropriate Activity-based test rule.
Step 2: Create a Compose Test Class
app/
└── src/
└── androidTest/
└── java/com/example/app/
└── GreetingComposeTest.kt
Step 3: Import Compose Testing APIs
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.assertIsDisplayed
import org.junit.Rule
import org.junit.Test
11. Creating a Simple Compose UI
Before writing a test, create a composable that displays a greeting and a button. When the button is tapped, the displayed text should update.
Example Compose Screen
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.*
@Composable
fun GreetingScreen() {
var greeting by remember {
mutableStateOf("Welcome")
}
Column {
Text(text = greeting)
Button(
onClick = {
greeting = "Hello, Asha!"
}
) {
Text("Greet")
}
}
}
The composable uses remember and mutableStateOf to maintain its displayed greeting during recomposition.
12. Writing Your First Compose UI Test
Compose testing uses a test rule to provide content and interact with the UI through its semantics tree.
Example Test
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertTextEquals
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 GreetingComposeTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun clickingGreetButtonUpdatesMessage() {
composeTestRule.setContent {
GreetingScreen()
}
composeTestRule
.onNodeWithText("Welcome")
.assertIsDisplayed()
composeTestRule
.onNodeWithText("Greet")
.performClick()
composeTestRule
.onNodeWithText("Hello, Asha!")
.assertIsDisplayed()
composeTestRule
.onNodeWithText("Hello, Asha!")
.assertTextEquals("Hello, Asha!")
}
}
Explanation
createComposeRule() creates a rule for testing Compose content.
setContent renders the composable under test.
onNodeWithText() finds a UI node containing the specified text.
assertIsDisplayed() verifies that the matching node is displayed.
performClick() simulates a user click.
assertTextEquals() verifies the exact text of the matching node.
13. Understanding the Compose Semantics Tree
Compose testing relies on semantics to identify and interact with UI components. Semantics describe information such as text, labels, available actions, and the meaning of interface elements.
Unlike Espresso, which primarily locates Android Views, Compose testing locates nodes through the semantics exposed by composables and their modifiers.
Finding Nodes by Text
composeTestRule
.onNodeWithText("Submit")
.assertIsDisplayed()
Finding Nodes by Test Tag
Test tags are useful when text is not a stable or unique identifier.
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.material3.Button
import androidx.compose.material3.Text
Button(
modifier = Modifier.testTag("loginButton"),
onClick = { /* Handle login */ }
) {
Text("Login")
}
The test can locate the button using its tag:
import androidx.compose.ui.test.onNodeWithTag
composeTestRule
.onNodeWithTag("loginButton")
.assertIsDisplayed()
Benefits of Test Tags
- Provide stable identifiers for important UI elements.
- Reduce dependence on display text.
- Help distinguish repeated controls.
- Improve test readability when used consistently.
Use semantics and meaningful labels where possible. Test tags should complement, not replace, accessible descriptions and appropriate UI semantics.
14. Performing Common Actions in Compose Tests
| Action |
API |
Purpose |
| Find text |
onNodeWithText() |
Locate a node using visible text. |
| Find a test tag |
onNodeWithTag() |
Locate a tagged node. |
| Tap a button |
performClick() |
Simulate a click. |
| Enter text |
performTextInput() |
Enter text into an editable text field. |
| Replace text |
performTextReplacement() |
Replace the current text value. |
| Clear text |
performTextClearance() |
Clear the contents of a text field. |
| Scroll forward |
performScrollTo() |
Scroll a supported node into view. |
| Check display |
assertIsDisplayed() |
Verify that a node is displayed. |
| Check enabled state |
assertIsEnabled() |
Verify that a node is enabled. |
| Check text |
assertTextEquals() |
Verify the expected text. |
15. Testing Text Input in Compose
Text input is a common UI testing requirement for login forms, search screens, registration pages, and profile editors.
Example Compose Form
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.material3.OutlinedTextField
import androidx.compose.runtime.*
@Composable
fun NameForm() {
var name by remember {
mutableStateOf("")
}
var submittedName by remember {
mutableStateOf("")
}
Column {
OutlinedTextField(
value = name,
onValueChange = { name = it },
label = { Text("Name") }
)
Button(
onClick = {
submittedName = name.trim()
}
) {
Text("Submit")
}
if (submittedName.isNotEmpty()) {
Text("Submitted: $submittedName")
}
}
}
Testing Text Entry and Submission
import androidx.compose.ui.test.*
import androidx.compose.ui.test.junit4.createComposeRule
import org.junit.Rule
import org.junit.Test
class NameFormTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun submittingNameDisplaysSubmittedValue() {
composeTestRule.setContent {
NameForm()
}
composeTestRule
.onNodeWithText("Name")
.performTextInput("Asha")
composeTestRule
.onNodeWithText("Submit")
.performClick()
composeTestRule
.onNodeWithText("Submitted: Asha")
.assertIsDisplayed()
}
}
The test demonstrates text entry and submission. For production forms, test empty input, whitespace, invalid data, and error messages as well.
16. Testing Navigation and Multi-Screen Workflows
Navigation tests verify that user interactions lead to the correct destination and that important data remains available across screens.
Example Navigation Test Scenarios
- Tap a Login button and verify that the login screen appears.
- Submit valid credentials and verify that the dashboard opens.
- Tap Back and verify that navigation follows the expected behavior.
- Open a detail screen and confirm that the selected item's information is displayed.
- Verify that invalid actions do not navigate to unauthorized screens.
Compose Navigation Example
When testing a screen with a navigation callback, the callback can be replaced with a test function. This verifies that the UI requests the expected navigation without requiring a complete navigation graph.
@Composable
fun ContinueScreen(
onContinue: () -> Unit
) {
Button(onClick = onContinue) {
Text("Continue")
}
}
import androidx.compose.ui.test.*
import androidx.compose.ui.test.junit4.createComposeRule
import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
class ContinueScreenTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun continueButtonInvokesCallback() {
var wasClicked = false
composeTestRule.setContent {
ContinueScreen {
wasClicked = true
}
}
composeTestRule
.onNodeWithText("Continue")
.performClick()
composeTestRule.runOnIdle {
assertTrue(wasClicked)
}
}
}
For full navigation tests, launch the appropriate application screen and verify the destination UI after the navigation action.
17. Testing Loading and Error States
Applications frequently display progress indicators, error messages, retry buttons, and empty states while communicating with APIs or databases.
UI tests should verify that these states are displayed and removed at the correct time.
Example Scenarios
| Scenario |
Expected Result |
| Request begins |
Loading indicator appears. |
| Request succeeds |
Data appears and loading indicator disappears. |
| Request fails |
An appropriate error message appears. |
| User retries |
A new request is initiated according to the design. |
| No results are returned |
An appropriate empty-state message appears. |
Use fake repositories or controlled test state to make the test deterministic. Avoid depending on a live production service when the goal is to verify a particular UI state.
18. Synchronization and Handling Asynchronous UI Operations
UI tests can fail when they try to inspect a screen before an operation has finished. Examples include network requests, background work, animations, and state updates.
Espresso Synchronization
Espresso waits for supported UI activity to become idle before executing interactions and assertions. However, it cannot automatically detect every custom background operation. A custom IdlingResource may be needed for asynchronous work that Espresso does not track.
Compose Synchronization
Compose testing APIs synchronize with Compose UI work, including supported recompositions and animations. When a test involves external asynchronous work, such as a repository request, the test must ensure that the external operation is properly controlled or completed before asserting the final state.
Best Practices
- Prefer framework synchronization over arbitrary delays.
- Use controlled test data and deterministic fake dependencies.
- Use idling resources when Espresso needs help tracking custom asynchronous operations.
- Use Compose test synchronization and explicit waiting APIs when appropriate.
- Avoid adding fixed sleep intervals as the primary synchronization mechanism.
- Ensure that loading indicators and asynchronous state changes are tested deliberately.
19. Testing Lists, Scrolling, and Repeated Elements
Lists and grids are common in shopping apps, messaging applications, dashboards, and task managers. UI tests should verify that items are displayed correctly, scrolling works, and item interactions affect the expected record.
Espresso List Testing
For traditional Views, Espresso can interact with RecyclerView items using appropriate matchers and actions, including utilities from Espresso Contrib where needed.
Compose List Testing
For Compose LazyColumn or LazyRow components, tests can locate nodes using text or tags and use supported scrolling actions. Some lazy items are not composed until they become visible, so tests should scroll to them when necessary.
List Testing Checklist
- Verify that the correct list items are displayed.
- Verify that an empty list displays the correct message.
- Scroll to items that are initially off-screen.
- Tap a list item and verify the expected action.
- Confirm that deleted or updated items reflect the latest state.
- Test long lists and repeated item labels.
20. Testing Dialogs, Permissions, and System UI
Some application workflows depend on system dialogs or interfaces outside the normal application window. Examples include runtime permission dialogs, notification permissions, and settings screens.
Espresso is primarily designed for Views within the target application. Compose testing focuses on Compose semantics. UI Automator is often more suitable when tests must interact with system UI or other applications.
Important Test Scenarios
- Verify the app's behavior when a permission is granted.
- Verify the app's behavior when a permission is denied.
- Confirm that the feature handles unavailable permissions gracefully.
- Check that a dialog can be dismissed using the expected action.
- Verify that the application does not crash after returning from system settings.
- Test notification behavior according to the app's permission and configuration requirements.
21. Testing UI State Restoration
Android applications must often handle configuration changes, including screen rotation, resizing, and changes in window size. Testing state restoration helps ensure that important information is not unexpectedly lost.
Examples of State Restoration Tests
- Enter text in a form and recreate the Activity.
- Rotate the device and verify that the screen remains functional.
- Scroll a list and verify expected state retention where required.
- Verify that a selected item remains selected if the design requires it.
- Confirm that transient UI state is restored or reset according to requirements.
For Compose, StateRestorationTester can help test saveable state restoration in isolation. Activity recreation can also be used when validating behavior at the application level.
22. Running UI Tests in Android Studio
Android Studio allows developers to execute UI tests on emulators and physical Android devices.
- Open the Android project in Android Studio.
- Check the instrumented test dependencies and test runner configuration.
- Write an Espresso or Compose test in the
src/androidTest directory.
- Start an emulator or connect a compatible Android device.
- Select the test class or individual test method.
- Choose Run to execute the test.
- Review the test results and logs.
- Investigate failures and correct the underlying issue.
- Rerun the test and related regression tests.
Running Tests from the Command Line
./gradlew connectedDebugAndroidTest
This command runs the configured debug instrumented tests on connected devices or emulators. On Windows, use gradlew.bat connectedDebugAndroidTest. The project must be configured correctly, and a compatible device or emulator must be available.
23. Common UI Testing Errors and Solutions
| Problem |
Possible Cause |
Solution |
| View not found in Espresso |
Incorrect resource ID or view not displayed. |
Inspect the current hierarchy and verify the expected screen. |
| Compose node not found |
Incorrect text, tag, or semantics. |
Inspect the Compose semantics tree and confirm the node exists. |
| Test fails intermittently |
Uncontrolled asynchronous work or shared test state. |
Use deterministic data and appropriate synchronization. |
| Button click has no effect |
Incorrect action handler or disabled control. |
Verify the enabled state and application behavior. |
| Text assertion fails |
Unexpected formatting, localization, or UI state. |
Check the expected string and actual screen state. |
| Instrumented test fails to launch |
Runner, device, manifest, or build configuration issue. |
Review Gradle configuration and device logs. |
| Test is slow |
Too many full-screen tests or uncontrolled dependencies. |
Test components in isolation and reduce unnecessary setup. |
| Keyboard covers a control |
Input layout or keyboard handling issue. |
Use appropriate keyboard actions and verify the intended layout behavior. |
| Compose test cannot find text |
Text is merged into parent semantics or not currently composed. |
Inspect semantics, use appropriate matchers, and scroll if necessary. |
24. Best Practices for Reliable UI Tests
- Test meaningful user behavior instead of internal implementation details.
- Use Espresso for traditional View interfaces and Compose testing APIs for Compose interfaces.
- Use stable identifiers and accessible semantics.
- Keep tests independent and repeatable.
- Prefer small focused tests for individual UI components.
- Use end-to-end UI tests for critical user journeys.
- Use fakes or controlled dependencies instead of live services for predictable tests.
- Avoid fixed delays whenever reliable synchronization is available.
- Verify both successful and unsuccessful user actions.
- Test different screen sizes, font scales, and supported Android versions.
- Include accessibility checks and meaningful labels.
- Run important UI tests as part of continuous integration.
- Investigate flaky tests rather than repeatedly rerunning them without fixing the cause.
- Maintain a balanced test suite with unit, integration, and UI tests.
25. Practical Project: Testing a Login Screen
Create a login screen containing an email field, a password field, a Login button, and an area for error or success feedback.
Project Requirements
- Users can enter their email and password.
- Empty input is validated.
- The Login button initiates authentication.
- A loading indicator appears while authentication is in progress.
- Successful authentication navigates to the dashboard.
- Failed authentication displays an error message.
Suggested Test Cases
| Test ID |
Scenario |
Expected Result |
| LOGIN-UI-01 |
Open the login screen. |
Email, password, and Login controls are visible. |
| LOGIN-UI-02 |
Submit empty input. |
Required-field validation is displayed. |
| LOGIN-UI-03 |
Enter invalid credentials. |
An appropriate authentication error appears. |
| LOGIN-UI-04 |
Enter valid test credentials. |
The dashboard opens after successful authentication. |
| LOGIN-UI-05 |
Start authentication. |
The loading indicator is displayed as expected. |
| LOGIN-UI-06 |
Return from the dashboard. |
Navigation follows the defined application behavior. |
| LOGIN-UI-07 |
Rotate the device during form entry. |
The screen remains stable and required state is preserved. |
Project Implementation Workflow
- Build the login screen using XML Views or Jetpack Compose.
- Assign stable view IDs or appropriate Compose semantics and test tags.
- Implement input validation and authentication state handling.
- Write UI tests for the initial screen and input validation.
- Simulate authentication success and failure using controlled test data.
- Verify navigation and state changes.
- Run tests on an emulator or physical device.
- Document failures and fix the underlying problems.
26. Interview Questions and Answers
Q1. What is UI testing in Android?
Answer: UI testing verifies that application interface elements display correctly and respond properly to user interactions.
Q2. What is Espresso?
Answer: Espresso is an Android UI testing framework primarily used to automate interactions with traditional Android Views.
Q3. What is Compose UI Testing?
Answer: Compose UI Testing provides APIs for finding composables through semantics, performing actions, and verifying UI behavior.
Q4. What is the difference between Espresso and Compose testing?
Answer: Espresso primarily works with Android Views, while Compose testing APIs are designed for Jetpack Compose interfaces.
Q5. What is the purpose of onView()?
Answer: Espresso's onView() locates a View using a matcher so that a test can interact with it or verify its properties.
Q6. What is the purpose of onNodeWithText()?
Answer: It locates a Compose semantics node using matching text.
Q7. What is a Compose test tag?
Answer: A test tag is an identifier assigned through Compose semantics to help tests locate a particular UI element.
Q8. What is the purpose of an assertion in UI testing?
Answer: An assertion verifies that the application meets an expected condition, such as displaying a message or enabling a button.
Q9. Why is synchronization important in UI testing?
Answer: Synchronization helps tests wait until relevant UI operations are ready, reducing failures caused by timing differences.
Q10. What is an instrumented test?
Answer: An instrumented test runs with Android instrumentation on a device or emulator and can interact with Android framework components.
Q11. Can Espresso and Compose testing be used in the same application?
Answer: Yes. Applications containing both Views and Compose can use the appropriate testing framework for each part of the interface.
Q12. How can UI test flakiness be reduced?
Answer: Use stable selectors, deterministic test data, framework synchronization, controlled dependencies, and isolated test setup.
Q13. How do you test form validation?
Answer: Enter valid and invalid values, perform the relevant action, and verify the expected validation messages and resulting UI state.
Q14. What is the role of UI tests in regression testing?
Answer: UI regression tests verify that important user workflows continue to function after code changes.
Q15. Should every UI component have an end-to-end test?
Answer: No. Individual components can often be tested with focused tests, while end-to-end UI tests should prioritize important workflows and integration points.
27. Practice Exercises
- Create an Espresso test that verifies a button is visible and enabled.
- Write a Compose test that checks the initial text displayed by a screen.
- Test a form that rejects empty input.
- Create a test that enters text and verifies the submitted result.
- Test navigation from a product list to a product details screen.
- Verify the loading and error states of a data-driven screen.
- Write a UI test for an empty list and a populated list.
- Test a dialog's confirmation and cancellation actions.
- Verify that a screen behaves correctly after configuration changes.
- Run the tests on an emulator and record the results.
28. Quick Revision Checklist
- Espresso is primarily used for Android View-based UI testing.
- Compose UI Testing is designed for Jetpack Compose interfaces.
- Espresso uses
onView() and view matchers.
- Compose testing uses semantics nodes and APIs such as
onNodeWithText().
- Both frameworks support actions and assertions.
- Instrumented tests commonly reside in
src/androidTest.
- Espresso synchronizes with supported UI activity and registered idling resources.
- Compose testing coordinates with Compose UI updates and supported animations.
- Test success, failure, empty, and validation states.
- Use stable identifiers and deterministic test data.
- Avoid arbitrary delays and unnecessary dependencies on live services.
- Run critical UI tests regularly and investigate failures carefully.
29. Conclusion
Creating UI tests with Espresso or Compose Testing is an important skill for Android developers. Espresso provides a practical approach for testing traditional Android Views, while Compose testing APIs allow developers to test modern declarative interfaces through semantics, interactions, and assertions.
By testing buttons, text fields, validation, navigation, loading states, and complete user workflows, developers can detect defects before release. Reliable UI tests should use stable selectors, controlled test data, appropriate synchronization, and a balanced testing strategy alongside unit and integration tests.
30. Additional Learning Resources
Official Espresso Documentation: Android Developers – Espresso
Official Compose Testing Documentation: Android Developers – Test Your Compose Layout
Official UI Testing Guide: Android Developers – UI Testing
Explore Android App Development Training
Continue learning Android application development with JustAcademy. Explore the course details and demo registration options below.
Course Details: Android App Development Training
Register for a Demo: Register for a Course Demo
Official Website: JustAcademy