© Denys Zelenchuk 2019
Denys ZelenchukAndroid Espresso Revealedhttps://doi.org/10.1007/978-1-4842-4315-2_14

14. AndroidX Test Library

Denys Zelenchuk1 
(1)
Zürich, Switzerland
 

A May 2018 Google I/O event announced the AndroidX open source project, which is used to develop, test, package, version, and release libraries within Android Jetpack. AndroidX replaces the Android Support Library. One of its major improvements is the fact that AndroidX packages are separately maintained and updated, so you can update AndroidX Libraries in your project independently.

Since Android Test Libraries are part of the Android Support Library, AndroidX also contains the AndroidX Test Library. Its beta version was announced at the Google I/O event in May 2018. During the Android DevSummit 2018 event, Google announced its stable 1.0 version. The AndroidX Test Library eliminates the need to maintain many different testing tools, with styles and APIs that are used on different test levels.

For your convenience, the second branch is called androidx-espresso-revealed with a TO-DO sample application project that was migrated to AndroidX. In order to use it, just switch the branch using the git checkout androidx-espresso-revealed command. You may need to clean the project using the Android Studio Build ➤ Clean option or even use File ➤ Invalidate Cashes / Restart… to avoid build issues.

AndroidX Test Compared to the Testing Support Library

The test pyramid in Figure 14-1 shows different test levels that were isolated from each other by the technology and the tools used to test them.
../images/469090_1_En_14_Chapter/469090_1_En_14_Fig1_HTML.png
Figure 14-1

Testing pyramid

The problem was that tests from different levels were not portable; they were tied to the testing tools and the environment they were written on. With the AndroidX Test Library, this problem is resolved. Now you can use a single set of test libraries to write tests related to different test levels—unit, integration, and user interface (or end-to-end) tests.

As we are talking about UI end-to-end tests in this book, we will focus on the new features in the AndroidX Test Library compared to the Android Testing Support Library from the UI test perspective:
  • ApplicationProvider Provides the ability to retrieve the current application context in tests.

  • ActivityScenario and FragmentScenario — ActivityScenario provides APIs to start and drive an activity’s lifecycle state for testing. It works with arbitrary activities and works consistently across different versions of the Android framework. FragmentScenario provides an API to start and drive a fragment’s lifecycle state for testing. It works with arbitrary fragments and works consistently across different versions of the Android framework.

  • The new Truth Assertion Library Truth is a fluent assertions library that can be used as an alternative to JUnit- or Hamcrest-based assertions when constructing the validation step for your tests.

  • Migrated all other libraries and dependencies to androidx.test All the dependencies from the android.test.support library were migrated to androidx.test.

Configuring Projects for AndroidX Test

In order to start using AndroidX Test in a newly created project, you have to follow nearly the same steps as with the android.support library :
  1. 1.
    To ensure you will have most recent AndroidX Test Libraries, add Google’s Maven repository inside the build.gradle file as the following:
    allprojects {
        repositories {
            jcenter()
            google()
        }
    }
     
  2. 2.
    Add the AndroidX Test dependencies you need in the UI tests:
    // Espresso UI Testing
    androidTestImplementation "androidx.test.espresso:espresso-core:$rootProject.espressoVersion"
    androidTestImplementation "androidx.test.espresso:espresso-contrib:$rootProject.espressoVersion"
    androidTestImplementation "androidx.test.espresso:espresso-intents:$rootProject.espressoVersion"
    androidTestImplementation "androidx.test.espresso.idling:idling-concurrent:$rootProject.espressoVersion"
    androidTestImplementation "androidx.test.espresso:espresso-idling-resource:$rootProject.espressoVersion"
    androidTestImplementation "androidx.test.espresso:espresso-web:$rootProject.espressoVersionAndroidX"
    androidTestImplementation "androidx.test.espresso:espresso-accessibility:$rootProject.espressoVersion"
     
  3. 3.
    Add the AndroidX Test instrumentation runner:
    testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
     
  4. 4.
    To use the Android Test Orchestrator, add this line:
    testOptions {
        execution 'ANDROIDX_TEST_ORCHESTRATOR'
    }
     

This is enough to start writing UI tests, as we do in the sample TO-DO application.

Migrating to AndroidX

AndroidX migration is integrated into Android Studio IDE, so it is quite easy to migrate both main and test applications. To start the migration process, choose Refactor ➤ Migrate to AndroidX… from Android Studio menu. You can also right-click on the project inside the project view and select these menu options. See Figure 14-2.
../images/469090_1_En_14_Chapter/469090_1_En_14_Fig2_HTML.jpg
Figure 14-2

The Migrate to AndroidX option in Android Studio

Before the migration is initiated, you will be asked to back up your project just in case you have compile issues after the migration. You will be also warned about fixing migration errors manually depending on your project dependencies.

In a couple of minutes (depending on the project’s complexity), the whole project will be migrated to the AndroidX Library. It’s really simple and easy. After it’s automatically triggered, the gradle sync task all seems to be good, as it shows BUILD SUCCESSFUL. But (there is always a but), during the first test run, the following issue occurred just after the test started.

Proguard Obfuscation Issue When Migrating to AndroidX .
Started running tests
java.lang.NoClassDefFoundError: Failed resolution of: Landroidx/test/espresso/IdlingRegistry;
...
Caused by: java.lang.ClassNotFoundException: Didn't find class "androidx.test.espresso.IdlingRegistry" on path: DexPathList[[zip file "/system/framework/android.test.runner.jar", zip file "/system/framework/android.test.mock.jar", zip file "/data/app/com.example.android.architecture.blueprints.todoapp.mock.test-U-I7D8dt-qcnzY2buNTPzw==/base.apk", zip file "/data/app/com.example.android.architecture.blueprints.todoapp.mock-JWNE8BTGptjn2ZWTifC78Q==/base.apk"],nativeLibraryDirectories=[/data/app/com.example.android.architecture.blueprints.todoapp.mock.test-U-I7D8dt-qcnzY2buNTPzw==/lib/arm64, /data/app/com.example.android.architecture.blueprints.todoapp.mock-JWNE8BTGptjn2ZWTifC78Q==/lib/arm64, /system/lib64, /vendor/lib64]]
...
Tests ran to completion .

It turns out that AndroidX migration doesn’t care, for good or bad, about project proguard files, meaning that if there were some defined proguard rules for the android.support libraries, they are not touched. The following sample code contains an example of the not-migrated android.support library classes mentioned in the proguard-rules.pro TO-DO application.

Migrate to AndroidX… Doesn’t Migrate Proguard Files.
-keep class android.support.v4.widget.DrawerLayout { *; }
-keep class android.support.test.espresso.IdlingResource { *; }
-keep class android.support.test.espresso.IdlingRegistry { *; }

As mentioned, manual migration error fixes would be necessary.

ActivityScenario in UI Tests

ActivityScenario provides APIs to start and drive the activity’s lifecycle stage (for example, Stage.CREATED, Stage.RESUMED, Stage.DESTROYED, etc.) for testing. These APIs are more suitable to integration tests when each activity state can be tested quickly and easily. What is important for UI tests is the fact that they can be used instead of ActivityTestRule to launch the application under the test activity before each test run.

chapter14 . ActivityScenarioTest.kt.
/**
 * Sample of ActivityScenario.launch(Activity.class) method usage.
 */
@RunWith(AndroidJUnit4::class)
class ActivityScenarioTest {
    @Before
    fun launchTasksActivity() {
        ActivityScenario.launch(TasksActivity::class.java)
    }
    @Test
    fun activityScenarioLaunchSample() {
        openContextualActionModeOverflowMenu()
        onView(allOf(withId(R.id.title), withText(R.string.refresh))).perform(click())
    }
}

As you can see, it doesn’t differ much from a usage perspective. But you will not have an instance of ActivityTestRule and, as a result, cannot access the launched activity.

Using Truth Assertion Library in UI Tests

Although it was developed mostly for unit and integration tests, the Truth Assertion Library can provide benefits to UI tests as well. We used JUnit assertions in Chapter 8 to assert element presence on the screen in the UI automator tests. Let’s take a look at the difference between basic JUnit and Truth assertions .
Table 14-1

Basic JUnit and Truth Assertions Comparison

JUnit

Truth

assertEquals(b, a)

assertTrue(c)

assertTrue(d.contains(a))

assertThat(a).isEqualTo(b)

assertThat(c).isTrue()

assertThat(d).contains(a)

assertTrue(d.contains(a) && d.contains(b))

assertTrue(d.contains(a) || d.contains(b) || d.contains(c))

assertThat(d).containsAllOf(a, b)

assertThat(d).containsAnyOf(a, b, c)

To tell the truth, the Truth syntax is more readable, easier to write, and is similar to Hamcrest library methods, which we used while writing Espresso tests. Now moving to the test failure reporting part. It is important to have a meaningful and descriptive test failure stacktrace, so the failure analysis doesn’t require much time and effort. The TruthTest.kt class contains two tests that fail to demonstrate the failure reporting by both JUnit and Truth and compare them afterward.

chapter14 .TruthTest.kt. Both Tests Fail to Demonstrate the Stacktrace Difference Between JUnit and Truth Assertions .
@RunWith(AndroidJUnit4::class)
class TruthTest {
    private val uiDevice: UiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
    @Before
    fun launchTasksActivity() {
        ActivityScenario.launch(TasksActivity::class.java)
    }
    /**
     * Specifically fails the test using JUnit assertion.
     */
    @Test
    fun generatesJunitAssertionError() {
        val selector = uiDevice.findObject(UiSelector().resourceId(
                "com.example.android.architecture.blueprints.todoapp.mock:id/fab_add_task"))
        // JUnit assertion.
        assertFalse(
                "Element with selector $selector is present on the screen when it should not",
                selector.exists())
    }
    /**
     * Specifically fails the test using Truth assertion.
     */
    @Test
    fun generatesTruthAssertionError() {
        val selector = uiDevice.findObject(UiSelector().resourceId(
                "com.example.android.architecture.blueprints.todoapp.mock:id/fab_add_task"))
        // Truth assertion.
        assertThat(selector.exists()).isFalse()
    }
}

Here is a sample error stacktrace from an old JUnit assertTrue(MESSAGE, CONDITION) method and one generated by the TruthTest.generatesJunitAssertionError() method implemented in the chapter’s TO-DO sample.

JUnit Error Stacktrace Generated by Failure in the generatesJunitAssertionError() Test.
java.lang.AssertionError: Element with selector androidx.test.uiautomator.UiObject@ce91768 is present on the screen when it should not
at org.junit.Assert.fail(Assert.java:88)
at org.junit.Assert.assertTrue(Assert.java:41)
at org.junit.Assert.assertFalse(Assert.java:64)
at com.example.android.architecture.blueprints.todoapp.test.chapter14.TruthTest.generatesJunitAssertionError(TruthTest.kt:33)
at java.lang.reflect.Method.invoke(Native Method)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at androidx.test.internal.runner.junit4.statement.RunBefores.evaluate(RunBefores.java:80)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at androidx.test.ext.junit.runners.AndroidJUnit4.run(AndroidJUnit4.java:104)
at org.junit.runners.Suite.runChild(Suite.java:128)
at org.junit.runners.Suite.runChild(Suite.java:27)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
at androidx.test.internal.runner.TestExecutor.execute(TestExecutor.java:56)
at androidx.test.runner.AndroidJUnitRunner.onStart(AndroidJUnitRunner.java:388)
at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:2145)

It is worth mentioning that the JUnit assertTrue() method accepts the error description message that’s present in the first stacktrace line.

By contrast, here is the Truth assertThat(CONDITION).isFalse() sample error stacktrace.

Truth Error Stacktrace Generated by Failure in the generatesTruthAssertionError() Test.
expected to be false
at com.example.android.architecture.blueprints.todoapp.test.chapter14.TruthTest.generatesTruthAssertionError(TruthTest.kt:41)

Exercise 29

Migrating to AndroidX
  1. 1.

    Check out the master branch of the TO-DO application project and migrate it to AndroidX. After the migration is complete, choose Build ➤ Clean Project. Run some tests. If there are failures, analyze and fix them by updating the proguard rules or updating dependences in the build.gradle file.

     
  2. 2.

    Implement a test class with a test that launches application activity using ActivityScenario.launch(Activity.class) in the @Before method and then runs the test.

     
  3. 3.

    Implement a test using an UI automator testing framework that will use the JUnit Assertion Library to validate the test results. Make the test fail and observe the stacktrace.

     
  4. 4.

    Implement a test using an UI automator testing framework that will use the Truth Assertion Library to validate the test results. Make the test fail and observe the stacktrace.

     

Summary

In general, AndroidX Test is a nice step forward. It brings alignment among different test types in terms of testing tools and dependencies used. The main weakness so far is that it targets unit and integration tests with only tiny improvements on UI tests. But more will certainly come for UI tests in time and hopefully with higher frequency than with the testing support library.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.135.206.254