Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
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
160
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
270
The Unyielding Spirit Of Android - Droidcon NYC '23
adammc331
1
330
DC London: Composing With Confidence
adammc331
0
270
DC London: Behind The Screen
adammc331
0
24
The Imposter's Guide To Dependency Injection - DCSF22
adammc331
2
330
Caching With Apollo Android
adammc331
0
350
Creating A Better Developer Experience By Avoiding Legacy Code
adammc331
0
530
Take Control Of Your APIs With GraphQL
adammc331
0
330
Droidcon London: Espresso Patronum
adammc331
0
300
Other Decks in Programming
See All in Programming
AIコーディングの本質は“コード“ではなく“構造“だった / The essence of AI coding is not “code” but "structure
seike460
2
580
JAWS DAYS 2025 re_Cheers: WEB
komakichi
0
130
ドメイン駆動設計とXPで支える子どもの未来 / Domain-Driven Design and XP Supporting Children's Future
nrslib
0
340
ビカム・ア・コパイロット
ymd65536
1
170
マイコンでもRustのtestがしたい/KernelVM Kansai 11
tnishinaga
1
960
파급효과: From AI to Android Development
l2hyunwoo
0
170
Boast Code Party / RubyKaigi 2025 After Event
lemonade_37
0
120
Duke on CRaC with Jakarta EE
ivargrimstad
1
350
Feature Flag 自動お掃除のための TypeScript プログラム変換
azrsh
PRO
3
140
オープンソースコントリビュート入門
_katsuma
0
150
20250429 - CNTUG Meetup #67 / DevOps Taiwan Meetup #69 - Deep Dive into Tetragon: Building Runtime Security and Observability with eBPF
tico88612
0
190
Building an Application with TDD, DDD and Hexagonal Architecture - Isn't it a bit too much?
mufrid
0
210
Featured
See All Featured
Optimising Largest Contentful Paint
csswizardry
37
3.2k
Reflections from 52 weeks, 52 projects
jeffersonlam
349
20k
Facilitating Awesome Meetings
lara
54
6.4k
Code Review Best Practice
trishagee
68
18k
GitHub's CSS Performance
jonrohan
1031
460k
Templates, Plugins, & Blocks: Oh My! Creating the theme that thinks of everything
marktimemedia
30
2.4k
The Illustrated Children's Guide to Kubernetes
chrisshort
48
50k
Balancing Empowerment & Direction
lara
0
49
XXLCSS - How to scale CSS and keep your sanity
sugarenia
248
1.3M
RailsConf 2023
tenderlove
30
1.1k
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
14
1.5k
Designing Experiences People Love
moore
142
24k
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