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
130
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
240
The Unyielding Spirit Of Android - Droidcon NYC '23
adammc331
1
290
DC London: Composing With Confidence
adammc331
0
250
DC London: Behind The Screen
adammc331
0
20
The Imposter's Guide To Dependency Injection - DCSF22
adammc331
2
300
Caching With Apollo Android
adammc331
0
330
Creating A Better Developer Experience By Avoiding Legacy Code
adammc331
0
490
Take Control Of Your APIs With GraphQL
adammc331
0
250
Droidcon London: Espresso Patronum
adammc331
0
220
Other Decks in Programming
See All in Programming
Vue3の一歩踏み込んだパフォーマンスチューニング2024
hal_spidernight
3
3.1k
讓數據說話:用 Python、Prometheus 和 Grafana 講故事
eddie
0
350
PLoP 2024: The evolution of the microservice architecture pattern language
cer
PRO
0
1.6k
開発効率向上のためのリファクタリングの一歩目の選択肢 ~コード分割~ / JJUG CCC 2024 Fall
ryounasso
0
360
外部システム連携先が10を超えるシステムでのアーキテクチャ設計・実装事例
kiwasaki
1
230
カラム追加で増えるActiveRecordのメモリサイズ イメージできますか?
asayamakk
4
1.6k
Modern Angular: Renovation for Your Applications
manfredsteyer
PRO
0
210
役立つログに取り組もう
irof
26
8.7k
What’s New in Compose Multiplatform - A Live Tour (droidcon London 2024)
zsmb
1
340
macOS でできる リアルタイム動画像処理
biacco42
7
1.9k
gopls を改造したら開発生産性が高まった
satorunooshie
8
240
NSOutlineView何もわからん:( 前編 / I Don't Understand About NSOutlineView :( Pt. 1
usagimaru
0
140
Featured
See All Featured
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
31
2.7k
Imperfection Machines: The Place of Print at Facebook
scottboms
264
13k
Being A Developer After 40
akosma
86
590k
Art, The Web, and Tiny UX
lynnandtonic
296
20k
Building Applications with DynamoDB
mza
90
6.1k
ReactJS: Keep Simple. Everything can be a component!
pedronauck
664
120k
Creating an realtime collaboration tool: Agile Flush - .NET Oxford
marcduiker
25
1.8k
Building Flexible Design Systems
yeseniaperezcruz
327
38k
Ruby is Unlike a Banana
tanoku
96
11k
YesSQL, Process and Tooling at Scale
rocio
167
14k
Building an army of robots
kneath
302
42k
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
231
17k
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