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

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

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

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

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

    4
  6. Two Options For Compose Testing • Individual components • Activities

    @AdamMc331 #DCNYC22 4
  7. Compose Rule Setup class PrimaryButtonTest { /** * Use createComposeRule

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

    to start up a specific activity. */ @get:Rule val composeTestRule = createAndroidComposeRule<MainActivity>() } @AdamMc331 #DCNYC22 6
  9. Rendering Content @Test fun renderEnabledButton() { composeTestRule.setContent { PrimaryButton(...) }

    } @AdamMc331 #DCNYC22 7
  10. Test Recipe // Find component composeTestRule.onNodeWithText("Test Button") // Make assertion

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

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

    composeTestRule.onNode(...).assertIsEnabled() // Perform action composeTestRule.onNode(...).performClick() @AdamMc331 #DCNYC22 8
  13. Finding Components @AdamMc331 #DCNYC22 9

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

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

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

  17. Making Assertions @AdamMc331 #DCNYC22 13

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

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

    15
  20. Performing Actions @AdamMc331 #DCNYC22 16

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

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

  23. Test Tags @AdamMc331 #DCNYC22 19

  24. // In app PrimaryButton( modifier = Modifier.testTag("login_button") ) // In

    test composeTestRule.onNodeWithTag("login_button") @AdamMc331 #DCNYC22 20
  25. Let's Test A Component @AdamMc331 #DCNYC22 21

  26. @Composable fun PrimaryButton( text: String, onClick: () -> Unit, enabled:

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

    Boolean = true, ) @AdamMc331 #DCNYC22 22
  28. Setup @RunWith(AndroidJUnit4::class) class PrimaryButtonTest { @get:Rule val composeTestRule = createComposeRule()

    @Test fun handleClickWhenEnabled() { // ... } } @AdamMc331 #DCNYC22 23
  29. Render Content var wasClicked = false composeTestRule.setContent { PrimaryButton( text

    = "Test Button", onClick = { wasClicked = true }, enabled = true, ) } @AdamMc331 #DCNYC22 24
  30. Verify Behavior composeTestRule.onNodeWithText("Test Button").performClick() assertTrue(wasClicked) @AdamMc331 #DCNYC22 25

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

  32. @AdamMc331 #DCNYC22 27

  33. Setup @RunWith(AndroidJUnit4::class) class MainActivityTest { @get:Rule val composeTestRule = createAndroidComposeRule<MainActivity>()

    @Test fun successfulLogin() { // ... } } @AdamMc331 #DCNYC22 28
  34. Verify Login Button Disabled composeTestRule .onNodeWithTag("login_button") .assertIsNotEnabled() @AdamMc331 #DCNYC22 29

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

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

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

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

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

  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
  41. Test Robots @AdamMc331 #DCNYC22 36

  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
  43. LoginScreenRobot class LoginScreenRobot { fun enterUsername(username: String) { usernameInput.performTextInput(username) }

    fun enterPassword(password: String) { passwordInput.performTextInput(password) } } @AdamMc331 #DCNYC22 38
  44. Kotlin Magic fun loginScreenRobot( composeTestRule: ComposeTestRule, block: LoginScreenRobot.() -> Unit,

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

    40
  46. @Test fun successfulLogin() { loginScreenRobot(composeTestRule) { verifyLoginButtonDisabled() enterUsername("adammc331") enterPassword("Hunter2") verifyLoginButtonEnabled()

    clickLoginButton() } homeScreenRobot(composeTestRule) { verifyLabelDisplayed() } } @AdamMc331 #DCNYC22 41
  47. Other Testing Options @AdamMc331 #DCNYC22 42

  48. Shot @AdamMc331 #DCNYC22 43

  49. Paparazzi @AdamMc331 #DCNYC22 44

  50. @AdamMc331 #DCNYC22 45

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

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

  53. @AdamMc331 #DCNYC22 48

  54. Thank You! @AdamMc331 #DCNYC22 49