Lock in $30 Savings on PRO—Offer Ends Soon! ⏳
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Composing With Confidence
Search
Adam McNeilly
September 01, 2022
Programming
1
180
Composing With Confidence
Presentation about testing in Jetpack Compose at Droidcon NYC 2022.
Adam McNeilly
September 01, 2022
Tweet
Share
More Decks by Adam McNeilly
See All by Adam McNeilly
MVWTF 2024: Demystifying Architecture Patterns
adammc331
1
290
The Unyielding Spirit Of Android - Droidcon NYC '23
adammc331
1
360
DC London: Composing With Confidence
adammc331
0
290
DC London: Behind The Screen
adammc331
0
46
The Imposter's Guide To Dependency Injection - DCSF22
adammc331
2
360
Caching With Apollo Android
adammc331
0
380
Creating A Better Developer Experience By Avoiding Legacy Code
adammc331
0
560
Take Control Of Your APIs With GraphQL
adammc331
0
380
Droidcon London: Espresso Patronum
adammc331
0
360
Other Decks in Programming
See All in Programming
Herb to ReActionView: A New Foundation for the View Layer @ San Francisco Ruby Conference 2025
marcoroth
0
240
AIコードレビューがチームの"文脈"を 読めるようになるまで
marutaku
0
310
「コードは上から下へ読むのが一番」と思った時に、思い出してほしい話
panda728
PRO
1
1.3k
複数人でのCLI/Infrastructure as Codeの暮らしを良くする
shmokmt
5
2.1k
Querying Design System デザインシステムの意思決定を支える構造検索
ikumatadokoro
1
1.2k
【CA.ai #3】Google ADKを活用したAI Agent開発と運用知見
harappa80
0
260
Module Harmony
petamoriken
2
610
[堅牢.py #1] テストを書かない研究者に送る、最初にテストを書く実験コード入門 / Let's start your ML project by writing tests
shunk031
11
6.9k
CSC305 Lecture 17
javiergs
PRO
0
270
関数の挙動書き換える
takatofukui
4
770
dotfiles 式年遷宮 令和最新版
masawada
1
670
Navigation 3: 적응형 UI를 위한 앱 탐색
fornewid
1
120
Featured
See All Featured
The MySQL Ecosystem @ GitHub 2015
samlambert
251
13k
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
35
3.3k
JavaScript: Past, Present, and Future - NDC Porto 2020
reverentgeek
52
5.7k
How Fast Is Fast Enough? [PerfNow 2025]
tammyeverts
3
380
How to train your dragon (web standard)
notwaldorf
97
6.4k
Building Flexible Design Systems
yeseniaperezcruz
329
39k
Bash Introduction
62gerente
615
210k
Helping Users Find Their Own Way: Creating Modern Search Experiences
danielanewman
31
3k
Java REST API Framework Comparison - PWX 2021
mraible
34
9k
Cheating the UX When There Is Nothing More to Optimize - PixelPioneers
stephaniewalter
285
14k
Designing Experiences People Love
moore
142
24k
Save Time (by Creating Custom Rails Generators)
garrettdimon
PRO
32
1.8k
Transcript
Composing With Confidence Adam McNeilly - @AdamMc331 @AdamMc331 #DCNYC22 1
With New Tools Comes New Responsibilities @AdamMc331 #DCNYC22 2
Getting Started With Compose Testing1 1 https://goo.gle/compose-testing @AdamMc331 #DCNYC22 3
Two Options For Compose Testing @AdamMc331 #DCNYC22 4
Two Options For Compose Testing • Individual components @AdamMc331 #DCNYC22
4
Two Options For Compose Testing • Individual components • Activities
@AdamMc331 #DCNYC22 4
Compose Rule Setup class PrimaryButtonTest { /** * Use createComposeRule
to test individual composable functions. */ @get:Rule val composeTestRule = createComposeRule() } @AdamMc331 #DCNYC22 5
Compose Rule Setup class PrimaryButtonTest { /** * Use createAndroidComposeRule
to start up a specific activity. */ @get:Rule val composeTestRule = createAndroidComposeRule<MainActivity>() } @AdamMc331 #DCNYC22 6
Rendering Content @Test fun renderEnabledButton() { composeTestRule.setContent { PrimaryButton(...) }
} @AdamMc331 #DCNYC22 7
Test Recipe // Find component composeTestRule.onNodeWithText("Test Button") // Make assertion
composeTestRule.onNode(...).assertIsEnabled() // Perform action composeTestRule.onNode(...).performClick() @AdamMc331 #DCNYC22 8
Test Recipe // Find component composeTestRule.onNodeWithText("Test Button") // Make assertion
composeTestRule.onNode(...).assertIsEnabled() // Perform action composeTestRule.onNode(...).performClick() @AdamMc331 #DCNYC22 8
Test Recipe // Find component composeTestRule.onNodeWithText("Test Button") // Make assertion
composeTestRule.onNode(...).assertIsEnabled() // Perform action composeTestRule.onNode(...).performClick() @AdamMc331 #DCNYC22 8
Finding Components @AdamMc331 #DCNYC22 9
composeTestRule.onNode(matcher) composeTestRule.onNode(hasProgressBarRangeInfo(...)) composeTestRule.onNode(isDialog()) @AdamMc331 #DCNYC22 10
composeTestRule.onNode(matcher) composeTestRule.onNode(hasProgressBarRangeInfo(...)) composeTestRule.onNode(isDialog()) // Helpers composeTestRule.onNodeWithText("") composeTestRule.onNodeWithTag("") composeTestRule.onNodeWithContentDescription("") @AdamMc331 #DCNYC22
11
composeTestRule.onAllNodes(matcher) composeTestRule.onAllNodesWithText("") @AdamMc331 #DCNYC22 12
Making Assertions @AdamMc331 #DCNYC22 13
composeTestRule.onNode(...) .assert(matcher) composeTestRule.onNode(...) .assert(hasText("Test Button")) composeTestRule.onNode(...) .assert(isEnabled()) @AdamMc331 #DCNYC22 14
composeTestRule.onNode(...) .assert(hasText("Test Button")) // Helpers composeTestRule.onNode(...) .assertTextEquals("Test Button") @AdamMc331 #DCNYC22
15
Performing Actions @AdamMc331 #DCNYC22 16
composeTestRule.onNode(...) .performClick() composeTestRule.onNode(...) .performTextInput(...) @AdamMc331 #DCNYC22 17
Cheat Sheet2 2 https://developer.android.com/static/images/jetpack/compose/compose-testing-cheatsheet.png @AdamMc331 #DCNYC22 18
Test Tags @AdamMc331 #DCNYC22 19
// In app PrimaryButton( modifier = Modifier.testTag("login_button") ) // In
test composeTestRule.onNodeWithTag("login_button") @AdamMc331 #DCNYC22 20
Let's Test A Component @AdamMc331 #DCNYC22 21
@Composable fun PrimaryButton( text: String, onClick: () -> Unit, enabled:
Boolean = true, ) @AdamMc331 #DCNYC22 22
@Composable fun PrimaryButton( text: String, onClick: () -> Unit, enabled:
Boolean = true, ) @AdamMc331 #DCNYC22 22
Setup @RunWith(AndroidJUnit4::class) class PrimaryButtonTest { @get:Rule val composeTestRule = createComposeRule()
@Test fun handleClickWhenEnabled() { // ... } } @AdamMc331 #DCNYC22 23
Render Content var wasClicked = false composeTestRule.setContent { PrimaryButton( text
= "Test Button", onClick = { wasClicked = true }, enabled = true, ) } @AdamMc331 #DCNYC22 24
Verify Behavior composeTestRule.onNodeWithText("Test Button").performClick() assertTrue(wasClicked) @AdamMc331 #DCNYC22 25
Let's Write A Bigger Test @AdamMc331 #DCNYC22 26
@AdamMc331 #DCNYC22 27
Setup @RunWith(AndroidJUnit4::class) class MainActivityTest { @get:Rule val composeTestRule = createAndroidComposeRule<MainActivity>()
@Test fun successfulLogin() { // ... } } @AdamMc331 #DCNYC22 28
Verify Login Button Disabled composeTestRule .onNodeWithTag("login_button") .assertIsNotEnabled() @AdamMc331 #DCNYC22 29
Type Username composeTestRule .onNodeWithTag("username_text_field") .performTextInput("adammc331") @AdamMc331 #DCNYC22 30
Type Password composeTestRule .onNodeWithTag("password_text_field") .performTextInput("Hunter2") @AdamMc331 #DCNYC22 31
Verify Login Button Enabled composeTestRule .onNodeWithTag("login_button") .assertIsEnabled() @AdamMc331 #DCNYC22 32
Click Login Button composeTestRule .onNodeWithTag("login_button") .performClick() @AdamMc331 #DCNYC22 33
Verify Home Screen Displayed composeTestRule .onNodeWithTag("home_screen_label") .assertIsDisplayed() @AdamMc331 #DCNYC22 34
@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
Test Robots @AdamMc331 #DCNYC22 36
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
LoginScreenRobot class LoginScreenRobot { fun enterUsername(username: String) { usernameInput.performTextInput(username) }
fun enterPassword(password: String) { passwordInput.performTextInput(password) } } @AdamMc331 #DCNYC22 38
Kotlin Magic fun loginScreenRobot( composeTestRule: ComposeTestRule, block: LoginScreenRobot.() -> Unit,
) { LoginScreenRobot(composeTestRule).apply(block) } @AdamMc331 #DCNYC22 39
loginScreenRobot(composeTestRule) { verifyLoginButtonDisabled() enterUsername("adammc331") enterPassword("Hunter2") verifyLoginButtonEnabled() clickLoginButton() } @AdamMc331 #DCNYC22
40
@Test fun successfulLogin() { loginScreenRobot(composeTestRule) { verifyLoginButtonDisabled() enterUsername("adammc331") enterPassword("Hunter2") verifyLoginButtonEnabled()
clickLoginButton() } homeScreenRobot(composeTestRule) { verifyLabelDisplayed() } } @AdamMc331 #DCNYC22 41
Other Testing Options @AdamMc331 #DCNYC22 42
Shot @AdamMc331 #DCNYC22 43
Paparazzi @AdamMc331 #DCNYC22 44
@AdamMc331 #DCNYC22 45
One More Repo... @AdamMc331 #DCNYC22 46
Composing With Confidence Sample Project @AdamMc331 #DCNYC22 47
@AdamMc331 #DCNYC22 48
Thank You! @AdamMc331 #DCNYC22 49