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
190
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
310
The Unyielding Spirit Of Android - Droidcon NYC '23
adammc331
1
370
DC London: Composing With Confidence
adammc331
0
300
DC London: Behind The Screen
adammc331
0
61
The Imposter's Guide To Dependency Injection - DCSF22
adammc331
2
380
Caching With Apollo Android
adammc331
0
390
Creating A Better Developer Experience By Avoiding Legacy Code
adammc331
0
580
Take Control Of Your APIs With GraphQL
adammc331
0
440
Droidcon London: Espresso Patronum
adammc331
0
420
Other Decks in Programming
See All in Programming
[KNOTS 2026登壇資料]AIで拡張‧交差する プロダクト開発のプロセス および携わるメンバーの役割
hisatake
0
240
AIと一緒にレガシーに向き合ってみた
nyafunta9858
0
160
AI Agent の開発と運用を支える Durable Execution #AgentsInProd
izumin5210
7
2.3k
16年目のピクシブ百科事典を支える最新の技術基盤 / The Modern Tech Stack Powering Pixiv Encyclopedia in its 16th Year
ahuglajbclajep
5
980
AI時代のキャリアプラン「技術の引力」からの脱出と「問い」へのいざない / tech-gravity
minodriven
20
6.4k
20260127_試行錯誤の結晶を1冊に。著者が解説 先輩データサイエンティストからの指南書 / author's_commentary_ds_instructions_guide
nash_efp
0
880
CSC307 Lecture 07
javiergs
PRO
0
550
AIで開発はどれくらい加速したのか?AIエージェントによるコード生成を、現場の評価と研究開発の評価の両面からdeep diveしてみる
daisuketakeda
1
970
Vibe codingでおすすめの言語と開発手法
uyuki234
0
220
ZJIT: The Ruby 4 JIT Compiler / Ruby Release 30th Anniversary Party
k0kubun
1
390
例外処理とどう使い分ける?Result型を使ったエラー設計 #burikaigi
kajitack
16
6k
Automatic Grammar Agreementと Markdown Extended Attributes について
kishikawakatsumi
0
180
Featured
See All Featured
Neural Spatial Audio Processing for Sound Field Analysis and Control
skoyamalab
0
160
End of SEO as We Know It (SMX Advanced Version)
ipullrank
3
3.9k
The SEO Collaboration Effect
kristinabergwall1
0
350
The Illustrated Guide to Node.js - THAT Conference 2024
reverentgeek
0
250
Building Adaptive Systems
keathley
44
2.9k
The SEO identity crisis: Don't let AI make you average
varn
0
62
Exploring anti-patterns in Rails
aemeredith
2
250
The untapped power of vector embeddings
frankvandijk
1
1.6k
Fireside Chat
paigeccino
41
3.8k
The Psychology of Web Performance [Beyond Tellerrand 2023]
tammyeverts
49
3.3k
Leadership Guide Workshop - DevTernity 2021
reverentgeek
1
200
brightonSEO & MeasureFest 2025 - Christian Goodrich - Winning strategies for Black Friday CRO & PPC
cargoodrich
3
97
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