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
220
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Composing With Confidence
Presentation about testing in Jetpack Compose at Droidcon NYC 2022.
Adam McNeilly
September 01, 2022
More Decks by Adam McNeilly
See All by Adam McNeilly
MVWTF 2024: Demystifying Architecture Patterns
adammc331
1
330
The Unyielding Spirit Of Android - Droidcon NYC '23
adammc331
1
410
DC London: Composing With Confidence
adammc331
0
320
DC London: Behind The Screen
adammc331
0
94
The Imposter's Guide To Dependency Injection - DCSF22
adammc331
2
400
Caching With Apollo Android
adammc331
0
420
Creating A Better Developer Experience By Avoiding Legacy Code
adammc331
0
620
Take Control Of Your APIs With GraphQL
adammc331
0
470
Droidcon London: Espresso Patronum
adammc331
0
460
Other Decks in Programming
See All in Programming
The Bowling Game- From Imperative to Functional Programming - Part 1
philipschwarz
PRO
0
310
霧の中の代数的エフェクト
funnyycat
1
340
Haskell/Servantを通してWebミドルウェアを捉え直す
pizzacat83
1
480
共通化で考えるべきは、実装より公開する型だった
codeegg
0
210
Webフレームワークの ベンチマークについて
yusukebe
0
200
言語を使う側から、作る側へ。 自作 Lisp で得た新たな気づき。
andpad
0
110
SREは、MCPとSRE Agentをこう使え!
kazumax55
0
150
【やさしく解説 設計編・中級 #4】ルールの寿命と、システムの年輪
panda728
PRO
2
110
Vite+ Unified Toolchain for the Web
naokihaba
0
740
例外の正しい扱い方 そのエラー try-catchして大丈夫?
jinwatanabe
0
360
信頼性について考えてみる(SRE NEXT 2026 miniLT)
hayama17
0
160
「正の参照」と 「負の導出」で組む ハーネスエンジニアリング
cottpan
1
140
Featured
See All Featured
The Organizational Zoo: Understanding Human Behavior Agility Through Metaphoric Constructive Conversations (based on the works of Arthur Shelley, Ph.D)
kimpetersen
PRO
0
380
A Tale of Four Properties
chriscoyier
163
24k
Why Your Marketing Sucks and What You Can Do About It - Sophie Logan
marketingsoph
0
220
Making the Leap to Tech Lead
cromwellryan
135
10k
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
16
2k
Building Flexible Design Systems
yeseniaperezcruz
330
40k
Done Done
chrislema
186
16k
Leading Effective Engineering Teams in the AI Era
addyosmani
9
2.1k
The Hidden Cost of Media on the Web [PixelPalooza 2025]
tammyeverts
2
340
SEO for Brand Visibility & Recognition
aleyda
0
4.6k
Avoiding the “Bad Training, Faster” Trap in the Age of AI
tmiket
0
190
Visual Storytelling: How to be a Superhuman Communicator
reverentgeek
2
590
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