DEV Community

myougaTheAxo
myougaTheAxo

Posted on

Compose Testing Robot Pattern Complete Guide — High-Maintainability UI Test Design

What You'll Learn

Robot pattern (test structuring, Page Object-like design, Compose UI testing, custom assertions, test readability improvement) explained.


What is the Robot Pattern?

Separates actions (what to do) from assertions (what to verify) in UI tests. Dramatically improves test readability and maintainability.


Robot Definition

class LoginRobot(private val rule: ComposeTestRule) {
    fun enterEmail(email: String) = apply {
        rule.onNodeWithTag("email_field").performTextInput(email)
    }

    fun enterPassword(password: String) = apply {
        rule.onNodeWithTag("password_field").performTextInput(password)
    }

    fun clickLogin() = apply {
        rule.onNodeWithTag("login_button").performClick()
    }

    fun assertErrorShown(message: String) = apply {
        rule.onNodeWithText(message).assertIsDisplayed()
    }

    fun assertLoginSuccess() = apply {
        rule.onNodeWithTag("home_screen").assertIsDisplayed()
    }

    fun assertLoginButtonEnabled() = apply {
        rule.onNodeWithTag("login_button").assertIsEnabled()
    }

    fun assertLoginButtonDisabled() = apply {
        rule.onNodeWithTag("login_button").assertIsNotEnabled()
    }
}
Enter fullscreen mode Exit fullscreen mode

Test Implementation

@HiltAndroidTest
class LoginScreenTest {
    @get:Rule val composeRule = createComposeRule()

    private fun loginRobot() = LoginRobot(composeRule)

    @Before
    fun setup() {
        composeRule.setContent {
            LoginScreen(viewModel = FakeLoginViewModel())
        }
    }

    @Test
    fun validCredentials_navigatesToHome() {
        loginRobot()
            .enterEmail("test@example.com")
            .enterPassword("password123")
            .clickLogin()
            .assertLoginSuccess()
    }

    @Test
    fun invalidEmail_showsError() {
        loginRobot()
            .enterEmail("invalid")
            .enterPassword("password123")
            .clickLogin()
            .assertErrorShown("Invalid email format")
    }

    @Test
    fun emptyFields_loginButtonDisabled() {
        loginRobot()
            .assertLoginButtonDisabled()
    }
}
Enter fullscreen mode Exit fullscreen mode

Multi-Screen Robot Chaining

class HomeRobot(private val rule: ComposeTestRule) {
    fun clickSettings() = SettingsRobot(rule).also {
        rule.onNodeWithTag("settings_button").performClick()
    }

    fun assertUserName(name: String) = apply {
        rule.onNodeWithText(name).assertIsDisplayed()
    }
}

class SettingsRobot(private val rule: ComposeTestRule) {
    fun clickLogout() = LoginRobot(rule).also {
        rule.onNodeWithTag("logout_button").performClick()
    }

    fun toggleDarkMode() = apply {
        rule.onNodeWithTag("dark_mode_switch").performClick()
    }
}

// Test including screen transitions
@Test
fun logoutFlow() {
    loginRobot()
        .enterEmail("test@example.com")
        .enterPassword("password")
        .clickLogin()
        .assertLoginSuccess()

    HomeRobot(composeRule)
        .assertUserName("Test User")
        .clickSettings()
        .clickLogout()
        .assertErrorShown("")  // Back to LoginScreen
}
Enter fullscreen mode Exit fullscreen mode

LazyColumn Robot

class ItemListRobot(private val rule: ComposeTestRule) {
    fun scrollToItem(index: Int) = apply {
        rule.onNodeWithTag("item_list").performScrollToIndex(index)
    }

    fun clickItem(title: String) = apply {
        rule.onNodeWithText(title).performClick()
    }

    fun assertItemCount(count: Int) = apply {
        rule.onAllNodesWithTag("list_item").assertCountEquals(count)
    }

    fun swipeToDelete(title: String) = apply {
        rule.onNodeWithText(title).performTouchInput { swipeLeft() }
    }
}
Enter fullscreen mode Exit fullscreen mode

Summary

Element Role
Robot Action (operation) definition
apply Method chaining
Assertion Result verification
Screen transition Robot→Robot return
  • Robot pattern dramatically improves test readability
  • apply enables method chaining (fluent API)
  • Screen transitions are type-safe by returning new Robot
  • UI changes: modify Robot only, test body stays unchanged

Ready-Made Android App Templates

8 production-ready Android app templates with Jetpack Compose, MVVM, Hilt, and Material 3.

Browse templatesGumroad

Top comments (0)