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

DC London: Composing With Confidence

DC London: Composing With Confidence

Another version of Composing With Confidence, this time presented at Droidcon London.

Adam McNeilly

October 27, 2022
Tweet

More Decks by Adam McNeilly

Other Decks in Programming

Transcript

  1. Compose Rule Setup class PrimaryButtonTest { // When testing individual

    components, we can just create a compose rule. @get:Rule val composeTestRule = createComposeRule() } @AdamMc331 #DCLDN22 6
  2. Compose Rule Setup class PrimaryButtonTest { // When testing individual

    components, we can just create a compose rule. @get:Rule val composeTestRule = createComposeRule() // When testing activities, use androidComposeRule. @get:Rule val composeTestRule = createAndroidComposeRule<MainActivity>() } @AdamMc331 #DCLDN22 7
  3. Rendering Content class PrimaryButtonTest { // ... @Test fun renderEnabledButton()

    { composeTestRule.setContent { PrimaryButton( text = "Test Button", enabled = true, ) } } } @AdamMc331 #DCLDN22 8
  4. Test Recipe // Find component composeTestRule.onNodeWithText("Test Button") // Make assertion

    composeTestRule.onNodeWithText("Test Button") .assertIsEnabled() // Perform action composeTestRule.onNodeWithText("Test Button") .performClick() @AdamMc331 #DCLDN22 9
  5. Test Recipe // Find component composeTestRule.onNodeWithText("Test Button") // Make assertion

    composeTestRule.onNodeWithText("Test Button") .assertIsEnabled() // Perform action composeTestRule.onNodeWithText("Test Button") .performClick() @AdamMc331 #DCLDN22 9
  6. Test Recipe // Find component composeTestRule.onNodeWithText("Test Button") // Make assertion

    composeTestRule.onNodeWithText("Test Button") .assertIsEnabled() // Perform action composeTestRule.onNodeWithText("Test Button") .performClick() @AdamMc331 #DCLDN22 9
  7. Test Tags // In app PrimaryButton( modifier = Modifier.testTag("login_button") )

    // In test composeTestRule.onNodeWithTag("login_button") @AdamMc331 #DCLDN22 20
  8. Primary Button @Composable fun PrimaryButton( text: String, onClick: () ->

    Unit, enabled: Boolean = true, ) @AdamMc331 #DCLDN22 22
  9. Primary Button @Composable fun PrimaryButton( text: String, onClick: () ->

    Unit, enabled: Boolean = true, ) @AdamMc331 #DCLDN22 22
  10. Render Content var wasClicked = false composeTestRule.setContent { PrimaryButton( text

    = "Test Button", onClick = { wasClicked = true }, enabled = true, ) } @AdamMc331 #DCLDN22 24
  11. Test Setup @Test fun renderBlueWinner() { composeTestRule.setContent { PocketLeagueTheme {

    MatchCard( match = MatchDetailDisplayModel.blueWinner, ) } } } @AdamMc331 #DCLDN22 29
  12. Let's Debug @Test fun renderBlueWinner() { composeTestRule.setContent { ... }

    composeTestRule.onRoot().printToLog(tag = "BLUE_WINNER") } @AdamMc331 #DCLDN22 32
  13. Debug Output printToLog: Printing with useUnmergedTree = 'false' Node #1

    at (l=0.0, t=237.0, r=1080.0, b=609.0)px |-Node #2 at (l=0.0, t=237.0, r=1080.0, b=609.0)px // ... |-Node #6 at (l=115.0, t=436.0, r=332.0, b=495.0)px, Tag: 'blue_match_team_name' | Text = '[Knights [winner]]' | Actions = [GetTextLayoutResult] // ... @AdamMc331 #DCLDN22 33
  14. Assertions @Test fun renderBlueWinner() { composeTestRule.setContent { ... } composeTestRule

    .onNodeWithTag("blue_match_team_name") .assertTextEquals("Knights [winner]") composeTestRule .onNodeWithTag("orange_match_team_name") .assertTextEquals("G2 Esports") } @AdamMc331 #DCLDN22 34
  15. Assertions @Test fun renderBlueWinner() { composeTestRule.setContent { ... } composeTestRule

    .onNodeWithTag("blue_match_team_name") .assertTextEquals("Knights [winner]") composeTestRule .onNodeWithTag("orange_match_team_name") .assertTextEquals("G2 Esports") } @AdamMc331 #DCLDN22 34
  16. 36

  17. 37

  18. Sample class MatchCardPaparazziTest { @get:Rule val paparazzi = Paparazzi() @Test

    fun renderBlueTeamWinner() { paparazzi.snapshot { PocketLeagueTheme { MatchCard(match = MatchDetailDisplayModel.blueWinner) } } } } @AdamMc331 #DCLDN22 38
  19. 40

  20. The Right Tool? • Paparazzi has pixel perfect validation •

    Requires you to verfy snapshot @AdamMc331 #DCLDN22 41
  21. The Right Tool? • Paparazzi has pixel perfect validation •

    Requires you to verfy snapshot • Snapshots can change often @AdamMc331 #DCLDN22 41
  22. @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 #DCLDN22 52
  23. 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 #DCLDN22 54
  24. LoginScreenRobot class LoginScreenRobot { // ... fun enterUsername(username: String) {

    usernameInput.performTextInput(username) } fun enterPassword(password: String) { passwordInput.performTextInput(password) } @AdamMc331 #DCLDN22 55
  25. Kotlin Magic fun loginScreenRobot( composeTestRule: ComposeTestRule, block: LoginScreenRobot.() -> Unit,

    ) { val robot = LoginScreenRobot(composeTestRule) robot.invoke(block) } @AdamMc331 #DCLDN22 56