Slide 1

Slide 1 text

@TTGonda victoriagonda.com Getting Started With AndroidX Test Victoria Gonda

Slide 2

Slide 2 text

@TTGonda victoriagonda.com Why Test? • Make changes with confidence • Living documentation • Encourages maintainable, clean code

Slide 3

Slide 3 text

@TTGonda victoriagonda.com https://twitter.com/TTGonda/status/1100931379150360576

Slide 4

Slide 4 text

@TTGonda victoriagonda.com https://twitter.com/TTGonda/status/1101298440552759297

Slide 5

Slide 5 text

@TTGonda victoriagonda.com https://twitter.com/TTGonda/status/1101677434162814976

Slide 6

Slide 6 text

@TTGonda victoriagonda.com https://twitter.com/TTGonda/status/1102010808324767744

Slide 7

Slide 7 text

@TTGonda victoriagonda.com https://twitter.com/saraceni_br/status/1102643475793829888

Slide 8

Slide 8 text

@TTGonda victoriagonda.com Victoria Gonda • Android Engineer at Buffer • Author and Editor at RayWenderlich.com • Chicago native • Hedgehog and cat owner • she/her

Slide 9

Slide 9 text

@TTGonda victoriagonda.com Testing Tools • JUnit4 • JUnit5 • Retrofit-mock • Espresso • Mockito • Robolectric • Spek • KotlinTest • MockK • Kakao • Hiroaki • Barista • Biscotti • AndroidX Test

Slide 10

Slide 10 text

@TTGonda victoriagonda.com AndroidX Test

Slide 11

Slide 11 text

@TTGonda victoriagonda.com AndroidX Test • Includes some of the libraries you already know, plus some new ones • Aims to solve the issue of too many libraries • Can be run both on device or on the JVM

Slide 12

Slide 12 text

@TTGonda victoriagonda.com What’s Inside?

Slide 13

Slide 13 text

@TTGonda victoriagonda.com What’s Inside? • JUnit • Core • Espresso • Truth Assertions

Slide 14

Slide 14 text

@TTGonda victoriagonda.com JUnit Core Espresso Truth

Slide 15

Slide 15 text

@TTGonda victoriagonda.com

Slide 16

Slide 16 text

@TTGonda victoriagonda.com JUnit Core Espresso Truth

Slide 17

Slide 17 text

@TTGonda victoriagonda.com JUnit

Slide 18

Slide 18 text

@TTGonda victoriagonda.com defaultConfig { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } testImplementation ‘junit:junit:4.12' testImplementation 'androidx.test.ext:junit:1.1.0'

Slide 19

Slide 19 text

@TTGonda victoriagonda.com @RunWith(AndroidJUnit4::class) class MainActivityTest

Slide 20

Slide 20 text

@TTGonda victoriagonda.com val context = ApplicationProvider.getApplicationContext() val appName = context.getString(R.string.app_name)

Slide 21

Slide 21 text

@TTGonda victoriagonda.com android { testOptions.unitTests.includeAndroidResources = true } testImplementation 'org.robolectric:robolectric:4.2.1'

Slide 22

Slide 22 text

@TTGonda victoriagonda.com JUnit Core Espresso Truth

Slide 23

Slide 23 text

@TTGonda victoriagonda.com Core

Slide 24

Slide 24 text

@TTGonda victoriagonda.com @Test fun mainActivityLaunches() { ActivityScenario.launch(MainActivity::class.java) }

Slide 25

Slide 25 text

@TTGonda victoriagonda.com val scenario = ActivityScenario .launch(MainActivity::class.java) scenario.moveToState(Lifecycle.State.CREATED) scenario.moveToState(Lifecycle.State.RESUMED) scenario.recreate() scenario.close()

Slide 26

Slide 26 text

@TTGonda victoriagonda.com val scenario = ActivityScenario .launch(MainActivity::class.java) scenario.moveToState(Lifecycle.State.CREATED) scenario.moveToState(Lifecycle.State.RESUMED) scenario.recreate() scenario.close()

Slide 27

Slide 27 text

@TTGonda victoriagonda.com val scenario = ActivityScenario .launch(MainActivity::class.java) scenario.moveToState(Lifecycle.State.CREATED) scenario.moveToState(Lifecycle.State.RESUMED) scenario.recreate() scenario.close()

Slide 28

Slide 28 text

@TTGonda victoriagonda.com val scenario = ActivityScenario .launch(MainActivity::class.java) scenario.moveToState(Lifecycle.State.CREATED) scenario.moveToState(Lifecycle.State.RESUMED) scenario.recreate() scenario.close()

Slide 29

Slide 29 text

@TTGonda victoriagonda.com @get:Rule val activityRule = ActivityScenarioRule(MainActivity::class.java) // in test function val scenario = activityRule.scenario

Slide 30

Slide 30 text

@TTGonda victoriagonda.com @get:Rule val activityRule = ActivityScenarioRule(MainActivity::class.java) // in test function val scenario = activityRule.scenario

Slide 31

Slide 31 text

@TTGonda victoriagonda.com testImplementation 'androidx.fragment:fragment-testing:1.1.0-alpha05'

Slide 32

Slide 32 text

@TTGonda victoriagonda.com val fragmentScenario = FragmentScenario.launch(TaskFragment::class.java) fragmentScenario.moveToState(Lifecycle.State.RESUMED)

Slide 33

Slide 33 text

@TTGonda victoriagonda.com val applicationInfo = ApplicationInfoBuilder .newBuilder() .setPackageName("com.victoriagonda.tasklist") .build() PackageInfoBuilder.newBuilder() .setApplicationInfo(applicationInfo) .build()

Slide 34

Slide 34 text

@TTGonda victoriagonda.com JUnit Core Espresso Truth

Slide 35

Slide 35 text

@TTGonda victoriagonda.com Espresso

Slide 36

Slide 36 text

@TTGonda victoriagonda.com testImplementation 'androidx.test.espresso:espresso-core:3.1.1'

Slide 37

Slide 37 text

@TTGonda victoriagonda.com ViewMatchers.withId ViewActions.click ViewAssertions.matches

Slide 38

Slide 38 text

@TTGonda victoriagonda.com @Test fun mainActivityShowsAddButton() { val scenario = ActivityScenario .launch(MainActivity::class.java) onView(withId(R.id.buttonAddList)) .check(matches(isDisplayed())) }

Slide 39

Slide 39 text

@TTGonda victoriagonda.com @Test fun mainActivityShowsAddButton() { val scenario = ActivityScenario .launch(MainActivity::class.java) onView(withId(R.id.buttonAddList)) .check(matches(isDisplayed())) }

Slide 40

Slide 40 text

@TTGonda victoriagonda.com testImplementation 'androidx.test.espresso:espresso-intents:3.1.1' testImplementation 'androidx.test.espresso:espresso-contrib:3.1.1'

Slide 41

Slide 41 text

@TTGonda victoriagonda.com @Test fun selectingListOpensDetailActivity() { ActivityScenario.launch(MainActivity::class.java) Intents.init() onView(withId(R.id.recyclerView)) .perform(RecyclerViewActions .actionOnItemAtPosition(0, click())) Intents.intended(IntentMatchers .hasComponent(DetailActivity::class.java.name)) Intents.release() }

Slide 42

Slide 42 text

@TTGonda victoriagonda.com @Test fun selectingListOpensDetailActivity() { ActivityScenario.launch(MainActivity::class.java) Intents.init() onView(withId(R.id.recyclerView)) .perform(RecyclerViewActions .actionOnItemAtPosition(0, click())) Intents.intended(IntentMatchers .hasComponent(DetailActivity::class.java.name)) Intents.release() }

Slide 43

Slide 43 text

@TTGonda victoriagonda.com @Test fun selectingListOpensDetailActivity() { ActivityScenario.launch(MainActivity::class.java) Intents.init() onView(withId(R.id.recyclerView)) .perform(RecyclerViewActions .actionOnItemAtPosition(0, click())) Intents.intended(IntentMatchers .hasComponent(DetailActivity::class.java.name)) Intents.release() }

Slide 44

Slide 44 text

@TTGonda victoriagonda.com @Test fun selectingListOpensDetailActivity() { ActivityScenario.launch(MainActivity::class.java) Intents.init() onView(withId(R.id.recyclerView)) .perform(RecyclerViewActions .actionOnItemAtPosition(0, click())) Intents.intended(IntentMatchers .hasComponent(DetailActivity::class.java.name)) Intents.release() }

Slide 45

Slide 45 text

@TTGonda victoriagonda.com @Test fun selectingListOpensDetailActivity() { ActivityScenario.launch(MainActivity::class.java) Intents.init() onView(withId(R.id.recyclerView)) .perform(RecyclerViewActions .actionOnItemAtPosition(0, click())) Intents.intended(IntentMatchers .hasComponent(DetailActivity::class.java.name)) Intents.release() }

Slide 46

Slide 46 text

@TTGonda victoriagonda.com @Test fun selectingListOpensDetailActivity() { ActivityScenario.launch(MainActivity::class.java) Intents.init() onView(withId(R.id.recyclerView)) .perform(RecyclerViewActions .actionOnItemAtPosition(0, click())) Intents.intended(IntentMatchers .hasComponent(DetailActivity::class.java.name)) Intents.release() }

Slide 47

Slide 47 text

@TTGonda victoriagonda.com @Test fun selectingListOpensDetailActivity() { ActivityScenario.launch(MainActivity::class.java) Intents.init() onView(withId(R.id.recyclerView)) .perform(RecyclerViewActions .actionOnItemAtPosition(0, click())) Intents.intended(IntentMatchers .hasComponent(DetailActivity::class.java.name)) Intents.release() }

Slide 48

Slide 48 text

@TTGonda victoriagonda.com JUnit Core Espresso Truth

Slide 49

Slide 49 text

@TTGonda victoriagonda.com Truth

Slide 50

Slide 50 text

@TTGonda victoriagonda.com testImplementation 'androidx.test.ext:truth:1.1.0'

Slide 51

Slide 51 text

@TTGonda victoriagonda.com @Test fun selectingListOpensDetailActivity() { ActivityScenario.launch(MainActivity::class.java) Intents.init() onView(withId(R.id.recyclerView)) .perform(RecyclerViewActions .actionOnItemAtPosition(0, click())) val intent = getIntents().first() IntentSubject.assertThat(intent) .hasComponentClass(DetailActivity::class.java) Intents.release() }

Slide 52

Slide 52 text

@TTGonda victoriagonda.com @Test fun selectingListOpensDetailActivity() { ActivityScenario.launch(MainActivity::class.java) Intents.init() onView(withId(R.id.recyclerView)) .perform(RecyclerViewActions .actionOnItemAtPosition(0, click())) val intent = getIntents().first() IntentSubject.assertThat(intent) .hasComponentClass(DetailActivity::class.java) Intents.release() }

Slide 53

Slide 53 text

@TTGonda victoriagonda.com val intent = DetailActivity.newIntent(tasklist, ApplicationProvider.getApplicationContext()) val bundleSubject: BundleSubject = assertThat(intent).extras() bundleSubject.containsKey("EXTRA_TASKLIST") val integerSubject: IntegerSubject = bundleSubject.integer("EXTRA_TASKLIST") integerSubject.isEqualTo(tasklist.id)

Slide 54

Slide 54 text

@TTGonda victoriagonda.com fun newIntent(list: Tasklist, context: Context): Intent { return Intent(context, DetailActivity::class.java) .apply { putExtra("EXTRA_TASKLIST", list.id) } }

Slide 55

Slide 55 text

@TTGonda victoriagonda.com val intent = DetailActivity.newIntent(tasklist, ApplicationProvider.getApplicationContext()) val bundleSubject: BundleSubject = assertThat(intent).extras() bundleSubject.containsKey("EXTRA_TASKLIST") val integerSubject: IntegerSubject = bundleSubject.integer("EXTRA_TASKLIST") integerSubject.isEqualTo(tasklist.id)

Slide 56

Slide 56 text

@TTGonda victoriagonda.com val intent = DetailActivity.newIntent(tasklist, ApplicationProvider.getApplicationContext()) val bundleSubject: BundleSubject = assertThat(intent).extras() bundleSubject.containsKey("EXTRA_TASKLIST") val integerSubject: IntegerSubject = bundleSubject.integer("EXTRA_TASKLIST") integerSubject.isEqualTo(tasklist.id)

Slide 57

Slide 57 text

@TTGonda victoriagonda.com val intent = DetailActivity.newIntent(tasklist, ApplicationProvider.getApplicationContext()) val bundleSubject: BundleSubject = assertThat(intent).extras() bundleSubject.containsKey("EXTRA_TASKLIST") val integerSubject: IntegerSubject = bundleSubject.integer("EXTRA_TASKLIST") integerSubject.isEqualTo(tasklist.id)

Slide 58

Slide 58 text

@TTGonda victoriagonda.com value of : intent.getComponent().getClassName() expected : com.victoriagonda.android.tasklist.DetailActivity but was : com.victoriagonda.android.tasklist.MainActivity

Slide 59

Slide 59 text

@TTGonda victoriagonda.com Wanted to match 1 intents. Actually matched 0 intents. IntentMatcher: has component: has component with: class name: is "com.victoriagonda.android.tasklist.DetailActivity" package name: an instance of java.lang.String short class name: an instance of java.lang.String Matched intents:[] Recorded intents: -Intent { cmp=com.victoriagonda.android.tasklist/.MainActivity (has extras) } handling packages:[[com.victoriagonda.android.tasklist]], extras:[Bundle[{EXTRA_TASKLIST=1}]])

Slide 60

Slide 60 text

@TTGonda victoriagonda.com value of : intent.getExtras() expected to contain key EXTRA_TASKLIST but was : Bundle[{EXTRA_TASKNAME=Laundry}]

Slide 61

Slide 61 text

@TTGonda victoriagonda.com value of : intent.getExtras().getInt(EXTRA_TASKLIST) expected : 4 but was : 5

Slide 62

Slide 62 text

@TTGonda victoriagonda.com junit.framework.AssertionFailedError: Expected :4 Actual :5

Slide 63

Slide 63 text

@TTGonda victoriagonda.com • MotionEvent • PointerCoords • PointerProperties • IntentCorrespondences • IntentCorrespondences • NotificationAction • Notification • PendingIntent

Slide 64

Slide 64 text

@TTGonda victoriagonda.com Wrapping Up • Google wrapped up all testing under one AndroidX roof • Four parts, JUnit, Core, Espresso, Truth • Can ALL be run on or off device • Hopefully means less APIs to learn and less worry about on or off device

Slide 65

Slide 65 text

@TTGonda victoriagonda.com Project Nitrogin?

Slide 66

Slide 66 text

@TTGonda victoriagonda.com Thanks! https://vgonda.github.io/Talk-Resources/