launchFragmentInContainer не удается разрешить действие для намерения

#android #unit-testing

#Android #модульное тестирование

Вопрос:

Я получаю следующую ошибку при попытке использовать FragmentScenario в инструментальном тестировании. Я считаю, что проблема связана с типом сборки, поскольку тесты выполняются в режиме «staging» вместо конфигурации сборки «debug».

Любая помощь будет принята с благодарностью.

Ошибка

 java.lang.RuntimeException: Unable to resolve activity for: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.example/androidx.fragment.app.testing.FragmentScenario$EmptyFragmentActivity (has extras) }
at androidx.test.core.app.InstrumentationActivityInvoker.startActivity(InstrumentationActivityInvoker.java:387)
at androidx.test.core.app.InstrumentationActivityInvoker.startActivity(InstrumentationActivityInvoker.java:416)
at androidx.test.core.app.ActivityScenario.launchInternal(ActivityScenario.java:265)
at androidx.test.core.app.ActivityScenario.launch(ActivityScenario.java:226)
at androidx.fragment.app.testing.FragmentScenario.internalLaunch(FragmentScenario.java:299)
at androidx.fragment.app.testing.FragmentScenario.launchInContainer(FragmentScenario.java:282)
at com.example.view.fragments.MainFragmentTest.testNavigation(MainFragmentTest.kt:139)
at java.lang.reflect.Method.invoke(Native Method)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at androidx.test.internal.runner.junit4.statement.RunBefores.evaluate(RunBefores.java:80)
at androidx.test.internal.runner.junit4.statement.RunAfters.evaluate(RunAfters.java:61)
at androidx.test.rule.ActivityTestRule$ActivityStatement.evaluate(ActivityTestRule.java:549)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at androidx.test.ext.junit.runners.AndroidJUnit4.run(AndroidJUnit4.java:154)
at org.junit.runners.Suite.runChild(Suite.java:128)
at org.junit.runners.Suite.runChild(Suite.java:27)
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
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:395)
at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:2189)
  

Выполняемый тест, который является точной копией документов от Google по тестированию навигации https://developer.android.com/guide/navigation/navigation-testing#kotlin

 @Test
fun testNavigation() {
    // Create a TestNavHostController
    val navController = TestNavHostController(ApplicationProvider.getApplicationContext())
    navController.setGraph(R.navigation.nav_graph)

    // Create a graphical FragmentScenario for the TitleScreen
    // Without adding androidTestImplementation "...fragment-testing" to the build gradle this errors and cannot build. This is because we are in staging variant instead of debug variant.
    val fragmentScenario = launchFragmentInContainer<MainFragment>()

    // Set the NavController property on the fragment
    fragmentScenario.onFragment { fragment ->
        Navigation.setViewNavController(fragment.requireView(), navController)
    }

    // Verify that performing a click changes the NavController’s state
    onView(withId(R.id.login)).perform(click())
    assertThat(navController.currentDestination?.id.toString(), equalTo(R.id.dashboardFragment.toString()))
}
  

Манифест

 <?xml version="1.0" encoding="utf-8"?>
<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.example">

    ....

    <application
        android:name=".Example"
        android:allowBackup="true"
        android:fullBackupContent="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:requestLegacyExternalStorage="true"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme"
        android:usesCleartextTraffic="${cleartextTrafficPermitted}">

        <activity
            android:name="com.example.controllers.LauncherActivity"
            android:screenOrientation="portrait"
            android:theme="@style/AppTheme.Launcher"
            tools:ignore="LockedOrientationActivity"
            android:windowSoftInputMode="stateHidden|adjustResize">

            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>

        </activity>

        <activity
            android:name="com.example.controllers.MainActivity"
            android:screenOrientation="portrait"
            android:windowSoftInputMode="stateHidden|adjustResize"
            android:theme="@style/AppTheme.Launcher"
            tools:ignore="LockedOrientationActivity" />

        ...

    </application>

</manifest>
  

build.gradle(приложение)

 apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
apply plugin: 'com.google.firebase.crashlytics'
apply plugin: 'com.google.gms.google-services'
apply plugin: "androidx.navigation.safeargs.kotlin"

ext.okhttp_version = '4.7.2'
ext.retrofit_version = '2.9.0'
ext.mockito_version = '3.3.3'
ext.coroutine_version = '1.3.9'
ext.test_version = '1.3.0'
ext.fragment_version = '1.2.5'
ext.nav_version = '2.3.0'

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.3"

    testBuildType "staging"

    defaultConfig {
        applicationId "com.example"
        minSdkVersion 23
        targetSdkVersion 29
        versionCode 1
        versionName "1.0.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

        vectorDrawables.useSupportLibrary = true
    }

    buildTypes {
        debug {
            buildConfigField "String", "API_DOMAIN", '"https://testing.com"'

            // This is needed when API's are connecting to HTTP endpoints. Primary purpose is
            // for the staging buildType to use a non-SSL mock server locally.
            manifestPlaceholders.cleartextTrafficPermitted ="true"
        }

        staging {
            initWith(buildTypes.debug)
            buildConfigField "String", "API_DOMAIN", '"http://127.0.0.1/"'
        }

        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'

            buildConfigField "String", "API_DOMAIN", '"https://production.com"'

            // Production version will not need to access HTTP endpoints. Disable
            // because it poses a security risk.
            manifestPlaceholders.cleartextTrafficPermitted ="false"
        }
    }

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    viewBinding {
        enabled = true
    }

    kotlinOptions {
        jvmTarget = "1.8"
    }

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

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    
    ...

    // Test dependencies
    testImplementation 'junit:junit:4.13'
    testImplementation "org.mockito:mockito-inline:$mockito_version"
    testImplementation "org.mockito:mockito-core:$mockito_version"

    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
    androidTestImplementation "org.mockito:mockito-android:$mockito_version"
    androidTestImplementation "androidx.test:core:$test_version"
    androidTestImplementation "androidx.test:rules:$test_version"
    androidTestImplementation "androidx.test:runner:$test_version"
    androidTestImplementation "com.squareup.okhttp3:mockwebserver:$okhttp_version"
    androidTestUtil "androidx.test:orchestrator:$test_version"

    // Without this, if I change the active build variant to staging I cannot use FragmentScenario as the IDE returns "unresolved reference"
    androidTestImplementation "androidx.fragment:fragment-testing:$fragment_version"

    // This is the correct implementation of fragment-testing
    debugImplementation "androidx.fragment:fragment-testing:$fragment_version"

    androidTestImplementation('com.android.support.test.espresso:espresso-contrib:3.0.2') {
        // Necessary to avoid version conflicts
        exclude group: 'com.android.support', module: 'appcompat'
        exclude group: 'com.android.support', module: 'support-v4'
        exclude group: 'com.android.support', module: 'support-annotations'
        exclude module: 'recyclerview-v7'
    }

    // Testing Navigation
    androidTestImplementation "androidx.navigation:navigation-testing:$nav_version"

    ...
}
  

Обновить

Если я удалю оба androidTestImplementation и debugImplementation и заменю на stagingImplementation, чтобы библиотека была доступна для промежуточного варианта сборки, это приведет к сбою всех тестовых классов с «не найдено тестов»

 // androidTestImplementation "androidx.fragment:fragment-testing:$fragment_version"
// debugImplementation "androidx.fragment:fragment-testing:$fragment_version"
//noinspection FragmentGradleConfiguration
stagingImplementation "androidx.fragment:fragment-testing:$fragment_version"
  

Ответ №1:

Если вы не выполняете свои тесты против debug сборок, debugImplementation этого недостаточно. Вам нужно добавить:

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

Комментарии:

1. Обратите внимание, что проверка Lint, включенная в fragment-testing , безусловно, может по-прежнему жаловаться на эту строку — она была построена в предположении, что вы тестируете на свой debug тип сборки и может быть безопасно подавлена, если это не относится к вашему конкретному случаю.

2. я знаю, я попробовал это, думая, что это так, и сразу почувствовал себя глупо, когда lint пожаловался… Я удалил androidTestImplementation и заменил debug на staging. Я также добавил //noinspection FragmentGradleConfiguration, чтобы lint не жаловался. Теперь я получаю сообщение «Тесты не найдены» при запуске теста. запуск журнала или logcat показывают ошибку.

3. Если он даже не приступает к запуску ваших тестов, это похоже на совершенно отдельную проблему, связанную с вашей конфигурацией Android Studio / run.

4. Когда я комментирую тестовую функцию testNavigation(), includes и stagingImplementation, все тесты выполняются, как ожидалось.

5. Вы хотите сказать, что если ваш тест закомментирован, и простое добавление stagingImplementation каким-то образом нарушает работу всей Android Studio и предотвращает запуск всех тестов?

Ответ №2:

В моем случае я использовал

debugImplementation "androidx.fragment:fragment-testing:1.5.0"

и я понизил его до

debugImplementation "androidx.fragment:fragment-testing:1.3.0-alpha08"

Это решило мою проблему, надеюсь, это поможет

Комментарии:

1. Это решило аналогичную проблему