Instrumental Testing with FragmentScenario

2019-06-27 11:15发布

I'm trying to use the new FragmentScenario APIs of the androidx testing libraries for local testing and instrumentation testing(androidTest). The api works fine in local environment but in instrumental testing, it gives error: java.lang.AssertionError: Activity never becomes requested state "[RESUMED, DESTROYED]" (last lifecycle transition = "PRE_ON_CREATE")"

Help me in instumental testing(androidTest)

Please check complete error detail:

java.lang.AssertionError: Activity never becomes requested state "[RESUMED, DESTROYED]" (last lifecycle transition = "PRE_ON_CREATE")
at androidx.test.core.app.ActivityScenario.waitForActivityToBecomeAnyOf(ActivityScenario.java:228)
at androidx.test.core.app.ActivityScenario.launch(ActivityScenario.java:198)
at androidx.fragment.app.testing.FragmentScenario.internalLaunch(FragmentScenario.java:169)
at androidx.fragment.app.testing.FragmentScenario.launchInContainer(FragmentScenario.java:160)
at com.techzis.avatr.LoginFragmentTest1.dummyTest(LoginFragmentTest1.kt:26)
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 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:2152)

Instrument Testing(androidTest) code is:

@RunWith(AndroidJUnit4::class)
class LoginFragmentTest1 {
    @Test
    fun dummyTest() {
        val scenario = launchFragmentInContainer<LoginFragment>()
        onView(ViewMatchers.withId(R.id.user_name)).perform(ViewActions.typeText("Hello World!"))
        onView(ViewMatchers.withId(R.id.user_name)).check(matches(withText("Hello World!")))
    }

}

Local unit test code is:

@RunWith(AndroidJUnit4::class)
@Config(application = MyApplication::class, shadows = [ShadowAndroidXMultiDex::class])
class LoginFragmentTest2 {
    @Test
    fun dummyTest() {
        val scenario = launchFragmentInContainer<LoginFragment>()
        onView(ViewMatchers.withId(R.id.user_name)).perform(ViewActions.typeText("Hello World!"))
        onView(ViewMatchers.withId(R.id.user_name)).check(matches(withText("Hello World!")))
    }

}

And app level build.gradle file is :

apply plugin: 'com.android.application'

apply plugin: 'kotlin-android'

apply plugin: 'kotlin-android-extensions'

apply plugin: 'kotlin-kapt'

apply plugin: "androidx.navigation.safeargs"

android {

    compileSdkVersion 28

    defaultConfig {
        applicationId "com.example"
        minSdkVersion 18
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        vectorDrawables.useSupportLibrary = true
        multiDexEnabled false
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        testInstrumentationRunnerArguments clearPackageData: 'true'
    }

    buildTypes {
        release {
            shrinkResources true
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

    dataBinding {
        enabled = true
    }

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    androidExtensions {
        experimental = true
    }

    lintOptions {
        checkReleaseBuilds false
        // Or, if you prefer, you can continue to check for errors in release builds,
        // but continue the build even when errors are found:
        abortOnError false
    }

    kapt {
        javacOptions {
            option("-Xmaxerrs", 1000)
        }
    }

    testOptions {
        unitTests.includeAndroidResources = true
        execution 'ANDROIDX_TEST_ORCHESTRATOR'
    }


    configurations.all {
        resolutionStrategy {
            force 'com.google.code.findbugs:jsr305:3.0.2'
            force 'org.jetbrains.kotlin:kotlin-reflect:1.2.71'
        }
    }
    sourceSets {
        test { java.srcDirs += "$projectDir/src/testShared" }
        androidTest {
            java.srcDirs += "$projectDir/src/testShared"
            resources.srcDirs += "$projectDir/src/test/resources"
        }
    }
}

dependencies {
    def lifecycle_version = "2.0.0"
    def fragment_version = "1.1.0-alpha01"
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'androidx.multidex:multidex:2.0.0'
    implementation 'androidx.appcompat:appcompat:1.0.2'
    implementation "androidx.fragment:fragment:$fragment_version"
    implementation 'com.google.android.material:material:1.0.0'
    implementation 'androidx.recyclerview:recyclerview:1.0.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.0.1'
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.1'
    implementation 'com.squareup.retrofit2:retrofit:2.4.0'
    implementation 'com.squareup.retrofit2:converter-moshi:2.4.0'
    implementation "com.squareup.moshi:moshi-kotlin:1.8.0"
    implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2'
    implementation 'com.squareup.okhttp3:logging-interceptor:3.9.1'
    implementation 'com.github.bumptech.glide:glide:4.8.0'
    kapt 'com.github.bumptech.glide:compiler:4.8.0'
    kapt "com.android.databinding:compiler:$gradle_version"
    implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
    implementation "android.arch.navigation:navigation-fragment-ktx:$nav_version"
    implementation 'com.github.florent37:diagonallayout:1.1.1'

    testImplementation 'androidx.test:core:1.0.0'
    testImplementation 'org.robolectric:robolectric:4.1-alpha-1'
    androidTestImplementation 'androidx.test:runner:1.1.0'
    testImplementation 'androidx.test:runner:1.1.0'
    androidTestImplementation 'androidx.test.ext:junit:1.0.0'
    testImplementation 'androidx.test.ext:junit:1.0.0'
    androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.0'
    testImplementation 'androidx.test.espresso:espresso-intents:3.1.0'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
    testImplementation 'androidx.test.espresso:espresso-core:3.1.0'
    androidTestImplementation 'androidx.test.ext:truth:1.0.0'
    testImplementation 'androidx.test.ext:truth:1.0.0'
    androidTestImplementation 'org.hamcrest:hamcrest-library:1.3'
    testImplementation 'org.hamcrest:hamcrest-library:1.3'
    androidTestImplementation "io.mockk:mockk-android:1.8.13.kotlin13"
    testImplementation "io.mockk:mockk:1.8.13.kotlin13"

    androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'

    androidTestImplementation "android.arch.navigation:navigation-testing:$nav_version" // Test helpers for navigation
    androidTestImplementation "androidx.fragment:fragment-testing:$fragment_version"
    testImplementation "androidx.fragment:fragment-testing:$fragment_version"
    androidTestImplementation "androidx.arch.core:core-testing:$lifecycle_version" // Test helpers for LiveData
    testImplementation "androidx.arch.core:core-testing:$lifecycle_version" // Test helpers for LiveData

    androidTestImplementation 'com.squareup.okhttp3:mockwebserver:3.10.0'
    testImplementation 'com.squareup.okhttp3:mockwebserver:3.10.0'

    androidTestUtil 'androidx.test:orchestrator:1.1.0'
}

3条回答
趁早两清
2楼-- · 2019-06-27 11:48

I believe there is a limitation within the launch(Intent startActivityIntent) method of ActivityScenario. It only wants the Activity to be RESUMED or DESTROYED and if it isn't within 4.5 seconds then it throws that error.

Within public static <A extends Activity> ActivityScenario<A> launch(Intent startActivityIntent) of Activity Scenario, check the logic scenario.waitForActivityToBecomeAnyOf(State.RESUMED, State.DESTROYED);

If you can create your own custom Activity Scenario and adjust this line of code to be something like scenario.waitForActivityToBecomeAnyOf(State.ON_PRE_CREATE, State.DESTROYED); then it will theoretically work for you.

This is happening because the Lifecycle.State of your Activity when launching the Fragment is not either of the two specific lifecycle states specified by ActivityScenario. Again, I believe this may be a limitation of the API and I am filing an issue under Android-Test Repo. https://github.com/android/android-test/issues/new

查看更多
走好不送
3楼-- · 2019-06-27 11:56

You need to add "fragment-testing" dependency to the APK under testing instead of test APK.

So please update your build.gradle to

debugImplementation "androidx.fragment:fragment-testing:$fragment_version"

from

androidTestImplementation "androidx.fragment:fragment-testing:$fragment_version"

(This is due to implementation details of FragmentScenario. "fragment-testing" declares Activity and is used by FragmentScenario. Activities declared in test APK runs in different process from APK under testing. In order to execute Fragment's code in the same process, you need to put "fragment-testing" library into your APK, not in test APK.)

Here's also a tutorial page in developers site.

查看更多
一夜七次
4楼-- · 2019-06-27 12:10

In my case the activity that the fragment is launched in, EmptyFragmentScenario, could not be opened because of a

Caused by: java.lang.ClassNotFoundException: Didn't find class androidx.fragment.app.testing.FragmentScenario$EmptyFragmentActivity

See the full stacktrace:

2018-12-12 02:12:46.529 32659-32659/? E/AndroidRuntime: FATAL EXCEPTION: main
    Process: app.debug.test, PID: 32659
    java.lang.RuntimeException: Unable to instantiate activity ComponentInfo{app.debug.test/androidx.fragment.app.testing.FragmentScenario$EmptyFragmentActivity}: java.lang.ClassNotFoundException: Didn't find class "androidx.fragment.app.testing.FragmentScenario$EmptyFragmentActivity" on path: DexPathList[[zip file "/system/framework/android.test.mock.jar", zip file "/system/framework/android.test.runner.jar", zip file "/data/app/app.debug.test-HdSyMEsvYzlt1aceQIeIuw==/base.apk"],nativeLibraryDirectories=[/data/app/app.test-HdSyMEsvYzlt1aceQIeIuw==/lib/arm64, /system/lib64, /vendor/lib64]]
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2843)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:193)
        at android.app.ActivityThread.main(ActivityThread.java:6669)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
     Caused by: java.lang.ClassNotFoundException: Didn't find class "androidx.fragment.app.testing.FragmentScenario$EmptyFragmentActivity" on path: DexPathList[[zip file "/system/framework/android.test.mock.jar", zip file "/system/framework/android.test.runner.jar", zip file "/data/app/app.debug.test-HdSyMEsvYzlt1aceQIeIuw==/base.apk"],nativeLibraryDirectories=[/data/app/app.debug.test-HdSyMEsvYzlt1aceQIeIuw==/lib/arm64, /system/lib64, /vendor/lib64]]
        at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:134)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:379)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
        at android.app.AppComponentFactory.instantiateActivity(AppComponentFactory.java:69)
        at android.app.Instrumentation.newActivity(Instrumentation.java:1215)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2831)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048) 
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78) 
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108) 
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808) 
        at android.os.Handler.dispatchMessage(Handler.java:106) 
        at android.os.Looper.loop(Looper.java:193) 
        at android.app.ActivityThread.main(ActivityThread.java:6669) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858) 
        Suppressed: java.lang.NoClassDefFoundError: Failed resolution of: Landroidx/fragment/app/FragmentActivity;
        at java.lang.VMClassLoader.findLoadedClass(Native Method)
        at java.lang.ClassLoader.findLoadedClass(ClassLoader.java:738)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:363)
                ... 15 more
     Caused by: java.lang.ClassNotFoundException: Didn't find class "androidx.fragment.app.FragmentActivity" on path: DexPathList[[zip file "/system/framework/android.test.mock.jar", zip file "/system/framework/android.test.runner.jar", zip file "/data/app/app.debug.test-HdSyMEsvYzlt1aceQIeIuw==/base.apk"],nativeLibraryDirectories=[/data/app/app.debug.test-HdSyMEsvYzlt1aceQIeIuw==/lib/arm64, /system/lib64, /vendor/lib64]]
        at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:134)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:379)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
                ... 18 more

I couldn't find the correct dependency I need in order for that EmptyFragmentActivity to be included at runtime so my temporary workaround was to not use launchFragmentInContainer and instead launch my own Activity:

My test activity:

class TestFragmentActivity : AppCompatActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
  }

  fun replaceFragment(fragment: Fragment) {
    supportFragmentManager
        .beginTransaction()
        .replace(android.R.id.content, fragment)
        .commit()
  }
}

My test:

@RunWith(AndroidJUnit4::class)
class MyFragmentAndroidTest {

  @get:Rule val activityRule: ActivityTestRule<TestFragmentActivity> =
      ActivityTestRule(TestFragmentActivity::class.java)

  @Test
  fun test() {
    activityRule.activity.replaceFragment(MyFragment.newInstance())

    onView(withId(R.id.title)).check(matches(withText("Title"))))
    // More assertions.
  }
}
查看更多
登录 后发表回答