Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Composing With Confidence

Composing With Confidence

Presentation about testing in Jetpack Compose at Droidcon NYC 2022.

Adam McNeilly

September 01, 2022
Tweet

More Decks by Adam McNeilly

Other Decks in Programming

Transcript

  1. Composing With Confidence
    Adam McNeilly - @AdamMc331
    @AdamMc331
    #DCNYC22 1

    View Slide

  2. With New Tools Comes New
    Responsibilities
    @AdamMc331
    #DCNYC22 2

    View Slide

  3. Getting Started With Compose
    Testing1
    1 https://goo.gle/compose-testing
    @AdamMc331
    #DCNYC22 3

    View Slide

  4. Two Options For Compose Testing
    @AdamMc331
    #DCNYC22 4

    View Slide

  5. Two Options For Compose Testing
    • Individual components
    @AdamMc331
    #DCNYC22 4

    View Slide

  6. Two Options For Compose Testing
    • Individual components
    • Activities
    @AdamMc331
    #DCNYC22 4

    View Slide

  7. Compose Rule Setup
    class PrimaryButtonTest {
    /**
    * Use createComposeRule to test individual composable functions.
    */
    @get:Rule
    val composeTestRule = createComposeRule()
    }
    @AdamMc331
    #DCNYC22 5

    View Slide

  8. Compose Rule Setup
    class PrimaryButtonTest {
    /**
    * Use createAndroidComposeRule to start up a specific activity.
    */
    @get:Rule
    val composeTestRule = createAndroidComposeRule()
    }
    @AdamMc331
    #DCNYC22 6

    View Slide

  9. Rendering Content
    @Test
    fun renderEnabledButton() {
    composeTestRule.setContent {
    PrimaryButton(...)
    }
    }
    @AdamMc331
    #DCNYC22 7

    View Slide

  10. Test Recipe
    // Find component
    composeTestRule.onNodeWithText("Test Button")
    // Make assertion
    composeTestRule.onNode(...).assertIsEnabled()
    // Perform action
    composeTestRule.onNode(...).performClick()
    @AdamMc331
    #DCNYC22 8

    View Slide

  11. Test Recipe
    // Find component
    composeTestRule.onNodeWithText("Test Button")
    // Make assertion
    composeTestRule.onNode(...).assertIsEnabled()
    // Perform action
    composeTestRule.onNode(...).performClick()
    @AdamMc331
    #DCNYC22 8

    View Slide

  12. Test Recipe
    // Find component
    composeTestRule.onNodeWithText("Test Button")
    // Make assertion
    composeTestRule.onNode(...).assertIsEnabled()
    // Perform action
    composeTestRule.onNode(...).performClick()
    @AdamMc331
    #DCNYC22 8

    View Slide

  13. Finding Components
    @AdamMc331
    #DCNYC22 9

    View Slide

  14. composeTestRule.onNode(matcher)
    composeTestRule.onNode(hasProgressBarRangeInfo(...))
    composeTestRule.onNode(isDialog())
    @AdamMc331
    #DCNYC22 10

    View Slide

  15. composeTestRule.onNode(matcher)
    composeTestRule.onNode(hasProgressBarRangeInfo(...))
    composeTestRule.onNode(isDialog())
    // Helpers
    composeTestRule.onNodeWithText("")
    composeTestRule.onNodeWithTag("")
    composeTestRule.onNodeWithContentDescription("")
    @AdamMc331
    #DCNYC22 11

    View Slide

  16. composeTestRule.onAllNodes(matcher)
    composeTestRule.onAllNodesWithText("")
    @AdamMc331
    #DCNYC22 12

    View Slide

  17. Making Assertions
    @AdamMc331
    #DCNYC22 13

    View Slide

  18. composeTestRule.onNode(...)
    .assert(matcher)
    composeTestRule.onNode(...)
    .assert(hasText("Test Button"))
    composeTestRule.onNode(...)
    .assert(isEnabled())
    @AdamMc331
    #DCNYC22 14

    View Slide

  19. composeTestRule.onNode(...)
    .assert(hasText("Test Button"))
    // Helpers
    composeTestRule.onNode(...)
    .assertTextEquals("Test Button")
    @AdamMc331
    #DCNYC22 15

    View Slide

  20. Performing Actions
    @AdamMc331
    #DCNYC22 16

    View Slide

  21. composeTestRule.onNode(...)
    .performClick()
    composeTestRule.onNode(...)
    .performTextInput(...)
    @AdamMc331
    #DCNYC22 17

    View Slide

  22. Cheat Sheet2
    2 https://developer.android.com/static/images/jetpack/compose/compose-testing-cheatsheet.png
    @AdamMc331
    #DCNYC22 18

    View Slide

  23. Test Tags
    @AdamMc331
    #DCNYC22 19

    View Slide

  24. // In app
    PrimaryButton(
    modifier = Modifier.testTag("login_button")
    )
    // In test
    composeTestRule.onNodeWithTag("login_button")
    @AdamMc331
    #DCNYC22 20

    View Slide

  25. Let's Test A Component
    @AdamMc331
    #DCNYC22 21

    View Slide

  26. @Composable
    fun PrimaryButton(
    text: String,
    onClick: () -> Unit,
    enabled: Boolean = true,
    )
    @AdamMc331
    #DCNYC22 22

    View Slide

  27. @Composable
    fun PrimaryButton(
    text: String,
    onClick: () -> Unit,
    enabled: Boolean = true,
    )
    @AdamMc331
    #DCNYC22 22

    View Slide

  28. Setup
    @RunWith(AndroidJUnit4::class)
    class PrimaryButtonTest {
    @get:Rule
    val composeTestRule = createComposeRule()
    @Test
    fun handleClickWhenEnabled() {
    // ...
    }
    }
    @AdamMc331
    #DCNYC22 23

    View Slide

  29. Render Content
    var wasClicked = false
    composeTestRule.setContent {
    PrimaryButton(
    text = "Test Button",
    onClick = {
    wasClicked = true
    },
    enabled = true,
    )
    }
    @AdamMc331
    #DCNYC22 24

    View Slide

  30. Verify Behavior
    composeTestRule.onNodeWithText("Test Button").performClick()
    assertTrue(wasClicked)
    @AdamMc331
    #DCNYC22 25

    View Slide

  31. Let's Write A Bigger Test
    @AdamMc331
    #DCNYC22 26

    View Slide

  32. @AdamMc331
    #DCNYC22 27

    View Slide

  33. Setup
    @RunWith(AndroidJUnit4::class)
    class MainActivityTest {
    @get:Rule
    val composeTestRule = createAndroidComposeRule()
    @Test
    fun successfulLogin() {
    // ...
    }
    }
    @AdamMc331
    #DCNYC22 28

    View Slide

  34. Verify Login Button Disabled
    composeTestRule
    .onNodeWithTag("login_button")
    .assertIsNotEnabled()
    @AdamMc331
    #DCNYC22 29

    View Slide

  35. Type Username
    composeTestRule
    .onNodeWithTag("username_text_field")
    .performTextInput("adammc331")
    @AdamMc331
    #DCNYC22 30

    View Slide

  36. Type Password
    composeTestRule
    .onNodeWithTag("password_text_field")
    .performTextInput("Hunter2")
    @AdamMc331
    #DCNYC22 31

    View Slide

  37. Verify Login Button Enabled
    composeTestRule
    .onNodeWithTag("login_button")
    .assertIsEnabled()
    @AdamMc331
    #DCNYC22 32

    View Slide

  38. Click Login Button
    composeTestRule
    .onNodeWithTag("login_button")
    .performClick()
    @AdamMc331
    #DCNYC22 33

    View Slide

  39. Verify Home Screen Displayed
    composeTestRule
    .onNodeWithTag("home_screen_label")
    .assertIsDisplayed()
    @AdamMc331
    #DCNYC22 34

    View Slide

  40. @Test
    fun successfulLogin() {
    composeTestRule
    .onNodeWithTag("login_button")
    .assertIsNotEnabled()
    composeTestRule
    .onNodeWithTag("username_text_field")
    .performTextInput("adammc331")
    composeTestRule
    .onNodeWithTag("login_button")
    .assertIsNotEnabled()
    composeTestRule
    .onNodeWithTag("password_text_field")
    .performTextInput("Hunter2")
    composeTestRule
    .onNodeWithTag("login_button")
    .assertIsEnabled()
    composeTestRule
    .onNodeWithTag("login_button")
    .performClick()
    composeTestRule
    .onNodeWithTag("home_screen_label")
    .assertIsDisplayed()
    }
    @AdamMc331
    #DCNYC22 35

    View Slide

  41. Test Robots
    @AdamMc331
    #DCNYC22 36

    View Slide

  42. LoginScreenRobot
    class LoginScreenRobot(
    composeTestRule: ComposeTestRule,
    ) {
    private val usernameInput = composeTestRule.onNodeWithTag("username_text_field")
    private val passwordInput = composeTestRule.onNodeWithTag("password_text_field")
    private val loginButton = composeTestRule.onNodeWithTag("login_button")
    }
    @AdamMc331
    #DCNYC22 37

    View Slide

  43. LoginScreenRobot
    class LoginScreenRobot {
    fun enterUsername(username: String) {
    usernameInput.performTextInput(username)
    }
    fun enterPassword(password: String) {
    passwordInput.performTextInput(password)
    }
    }
    @AdamMc331
    #DCNYC22 38

    View Slide

  44. Kotlin Magic
    fun loginScreenRobot(
    composeTestRule: ComposeTestRule,
    block: LoginScreenRobot.() -> Unit,
    ) {
    LoginScreenRobot(composeTestRule).apply(block)
    }
    @AdamMc331
    #DCNYC22 39

    View Slide

  45. loginScreenRobot(composeTestRule) {
    verifyLoginButtonDisabled()
    enterUsername("adammc331")
    enterPassword("Hunter2")
    verifyLoginButtonEnabled()
    clickLoginButton()
    }
    @AdamMc331
    #DCNYC22 40

    View Slide

  46. @Test
    fun successfulLogin() {
    loginScreenRobot(composeTestRule) {
    verifyLoginButtonDisabled()
    enterUsername("adammc331")
    enterPassword("Hunter2")
    verifyLoginButtonEnabled()
    clickLoginButton()
    }
    homeScreenRobot(composeTestRule) {
    verifyLabelDisplayed()
    }
    }
    @AdamMc331
    #DCNYC22 41

    View Slide

  47. Other Testing Options
    @AdamMc331
    #DCNYC22 42

    View Slide

  48. Shot
    @AdamMc331
    #DCNYC22 43

    View Slide

  49. Paparazzi
    @AdamMc331
    #DCNYC22 44

    View Slide

  50. @AdamMc331
    #DCNYC22 45

    View Slide

  51. One More Repo...
    @AdamMc331
    #DCNYC22 46

    View Slide

  52. Composing With Confidence Sample Project
    @AdamMc331
    #DCNYC22 47

    View Slide

  53. @AdamMc331
    #DCNYC22 48

    View Slide

  54. Thank You!
    @AdamMc331
    #DCNYC22 49

    View Slide