Espresso vs. Compose


Comprehensive Guide to UI Testing in Android

User Interface (UI) testing plays a crucial role in ensuring the functionality and user experience of Android applications. In this comprehensive guide, we’ll explore two powerful UI testing frameworks for Android: Espresso for traditional UI components and Compose for Jetpack Compose-based UIs.

Introduction to Espresso

Espresso is a popular UI testing framework for Android that provides a fluent API for writing concise and reliable tests. It allows developers to simulate user interactions and validate the behavior of UI components within their apps. Let’s delve deeper into Espresso testing with detailed examples and best practices.

Key Features of Espresso:

  • Synchronization: Espresso automatically waits for the app’s UI to be idle before performing actions, ensuring that tests run reliably.
  • Matcher Framework: Espresso provides a rich set of matchers to locate UI elements based on various criteria such as IDs, text, and content descriptions.
  • ViewActions: Actions like clicking, typing text, and scrolling can be performed on UI elements using predefined ViewActions.
  • Assertions: Espresso allows developers to assert UI states and properties, ensuring that the app behaves as expected.

Best Practices for Espresso Testing:

  • Isolation: Write tests that focus on testing individual UI components or specific user flows to ensure test reliability and maintainability.
  • Idling Resources: Use Espresso’s Idling Resources to handle asynchronous operations such as network requests or animations effectively.
  • Custom Matchers and Actions: Create custom matchers and actions when necessary to encapsulate complex UI interactions or verify specific states.
  • UI Automator Integration: Combine Espresso with the UI Automator framework to perform cross-app UI testing and interact with system-level UI components.

Example: Testing a RecyclerView

Consider an app with a RecyclerView displaying a list of items. Let’s write an Espresso test to verify that clicking on an item opens a detail view.

@RunWith(AndroidJUnit4::class)
class RecyclerViewTest {

    @Rule
    @JvmField
    val activityTestRule = ActivityTestRule(MainActivity::class.java)

    @Test
    fun clickRecyclerViewItem_OpensDetailView() {
        onView(withId(R.id.recycler_view)).perform(
            RecyclerViewActions.actionOnItemAtPosition<RecyclerView.ViewHolder>(
                0, click()
            )
        )

        onView(withId(R.id.detail_view)).check(matches(isDisplayed()))
    }
}

In this test, we use RecyclerViewActions to click on the first item in the RecyclerView and then verify that the detail view is opened.

Example: Testing Dialogs and Toasts

Espresso can also be used to test UI elements like dialogs and toasts. Let’s see examples of testing a dialog and a toast message.

@RunWith(AndroidJUnit4::class)
class DialogAndToastTest {

    @Rule
    @JvmField
    val activityTestRule = ActivityTestRule(MainActivity::class.java)

    @Test
    fun showDialog_DisplaysDialog() {
        onView(withId(R.id.show_dialog_button)).perform(click())

        onView(withText(R.string.dialog_message)).check(matches(isDisplayed()))
    }

    @Test
    fun showToast_DisplaysToast() {
        onView(withId(R.id.show_toast_button)).perform(click())

        onView(withText(R.string.toast_message)).inRoot(ToastMatcher())
            .check(matches(isDisplayed()))
    }
}

In these tests, we validate that clicking on a button triggers the display of a dialog or a toast message, respectively.

Introduction to Compose Testing

Jetpack Compose is a modern UI toolkit for building native Android UIs declaratively. Compose testing allows developers to write tests for Compose UI components in a concise and intuitive manner. Let’s explore Compose testing with detailed examples and best practices.

Key Features of Compose Testing:

  • ComposeTestRule: The ComposeTestRule provides a test rule for setting up and controlling Compose UI tests.
  • Compose Test Nodes: Compose tests interact with UI elements using Compose test nodes, which represent the structure of the UI hierarchy.
  • Actions and Assertions: Similar to Espresso, Compose testing provides actions to interact with UI elements and assertions to validate UI states.

Best Practices for Compose Testing:

  • Preview Testing: Leverage Compose’s preview functionality to visually inspect UI components and quickly iterate on UI tests.
  • State Testing: Test UI components with different states and inputs to ensure proper handling of state changes and user interactions.
  • Compose Test Utilities: Utilize Compose test utilities for common tasks like setting up themes, managing coroutine scopes, and handling animations.
  • Integration Testing: Combine Compose testing with other testing frameworks like Mockito for comprehensive integration testing of UI components and business logic.

Example: Testing TextFields and Buttons

Consider a Compose screen with TextFields for username and password, and a Button for login. Let’s write a Compose test to verify the login functionality.

@ExperimentalComposeUiApi
@Unpreview
@RunWith(AndroidJUnit4::class)
class LoginScreenTest {

    @get:Rule
    val composeTestRule = createAndroidComposeRule<MainActivity>()

    @Test
    fun loginButton_Click_SuccessfulLogin() {
        composeTestRule.setContent {
            LoginScreen()
        }

        composeTestRule.onNodeWithTag("username_field").performTextInput("example_username")
        composeTestRule.onNodeWithTag("password_field").performTextInput("example_password")
        composeTestRule.onNodeWithTag("login_button").performClick()

        composeTestRule.onNodeWithText("Welcome, example_username").assertExists()
    }
}

In this Compose test, we simulate entering text into TextFields and clicking on the login button. Then, we verify that the login is successful by checking for a welcome message on the next screen.

Example: Testing Stateful Composables

Compose allows developers to create stateful UI components easily. Let’s see an example of testing a stateful Composable, such as a counter.

@ExperimentalComposeUiApi
@Unpreview
@RunWith(AndroidJUnit4::class)
class CounterTest {

    @get:Rule
    val composeTestRule = createAndroidComposeRule<MainActivity>()

    @Test
    fun counterButton_Click_IncrementsCounter() {
        composeTestRule.setContent {
            Counter()
        }

        composeTestRule.onNodeWithTag("counter_button").performClick()
        composeTestRule.onNodeWithTag("counter_button").performClick()

        composeTestRule.onNodeWithText("Count: 2").assertExists()
    }
}

In this test, we click on the counter button twice and then verify that the counter displays the correct count.

Conclusion

Effective UI testing is essential for delivering high-quality Android applications. Espresso and Compose testing frameworks provide developers with powerful tools for testing UI components in both traditional and Jetpack Compose-based apps. By following best practices and leveraging these frameworks, developers can ensure the reliability, performance, and user satisfaction of their Android apps.