使用 Hilt 时因 ClassCastException 导致 Android 测试失败

Alw*_*ing 1 testing android android-studio dagger-hilt

我为 Android 应用程序编写 kotlin 代码已经有一段时间了,但我决定也开始为我的应用程序编写测试代码。我在使用 Hilt 时遇到了一些问题。我尝试的是:


    import android.app.Application
    
    open class AbstractApplication: Application()

Run Code Online (Sandbox Code Playgroud)

    @HiltAndroidApp
    class IgmeApplication : IgmeAbstractApplication() {  
    
      @Inject
      lateinit var authenticationManager: AuthenticationManager

             ....
}
Run Code Online (Sandbox Code Playgroud)

然后在Android测试目录中:


    import dagger.hilt.android.testing.CustomTestApplication
    
    @CustomTestApplication(AbstractApplication::class)
    open class HiltTestApplication

Run Code Online (Sandbox Code Playgroud)

    import android.app.Application
    import android.content.Context
    import androidx.test.runner.AndroidJUnitRunner
    
    class HiltTestRunner : AndroidJUnitRunner() {
    
        override fun newApplication(
            cl: ClassLoader?,
            className: String?,
            context: Context?
        ): Application {
            return super.newApplication(cl,HiltTestApp::class.java.name, context)
        }
    }

Run Code Online (Sandbox Code Playgroud)

我的测试课:


    @HiltAndroidTest
    class AuthenticationTest{
    
        @get:Rule
        var hiltRule = HiltAndroidRule(this)
    
        @Test
        fun useAppContext() {
            // Context of the app under test.
            val appContext = InstrumentationRegistry.getInstrumentation().targetContext
            Assert.assertEquals("com.crowdpolicy.onext.igme", appContext.packageName)
        }
    
        @Before
        fun setUp() {
            // Populate @Inject fields in test class
            hiltRule.inject()
        }
    
        @After
        fun tearDown() {
        }
    }

Run Code Online (Sandbox Code Playgroud)

我的应用程序级别 gradle 文件:


    plugins {
        id 'com.android.application'
        id 'kotlin-android'
        id 'kotlin-kapt'
        id 'com.google.gms.google-services'
        id 'com.google.firebase.crashlytics'
        id 'com.google.firebase.firebase-perf' // Firebase Performance monitoring
        id 'androidx.navigation.safeargs.kotlin'
        id 'dagger.hilt.android.plugin'
        id 'kotlin-parcelize'
        id 'com.google.protobuf'
    }
    
    Properties localProperties = new Properties()
    localProperties.load(new FileInputStream(rootProject.file('local.properties')))
    
    Properties keyStoreProperties = new Properties()
    keyStoreProperties.load(new FileInputStream(rootProject.file('keystore.properties')))
    
    android {
        buildToolsVersion "30.0.3"
        ndkVersion localProperties['ndk.version']
    
        signingConfigs {
            release {
                storeFile file(keyStoreProperties['key.release.path'])
                keyAlias 'igme-key'
                storePassword keyStoreProperties['key.release.keystorePassword']
                keyPassword keyStoreProperties['key.release.keyPassword']
            }
        }
    
        compileSdkVersion 30
        compileOptions {
            sourceCompatibility JavaVersion.VERSION_1_8
            targetCompatibility JavaVersion.VERSION_1_8
        }
    
     kotlinOptions {
            jvmTarget = JavaVersion.VERSION_1_8.toString()
            freeCompilerArgs += '-Xopt-in=kotlin.RequiresOptIn'
        }
    
        defaultConfig {
            applicationId "com.crowdpolicy.onext.igme"
            minSdkVersion 21
            targetSdkVersion 30
            versionCode 1
            versionName "1.0"
    
            testInstrumentationRunner "com.crowdpolicy.onext.igme.HiltTestRunner"
        }
    buildTypes {
            release {
                minifyEnabled true
                shrinkResources true
    
                debuggable false
    
                //signingConfig signingConfigs.release
    
                firebaseCrashlytics {
                    // Enable processing and uploading o FirebaseCrashlytics.getInstance()f native symbols to Crashlytics
                    // servers. By default, this is disabled to improve build speeds.
                    // This flag must be enabled to see properly-symbolicated native
                    // stack traces in the Crashlytics dashboard.
                    nativeSymbolUploadEnabled true
                    unstrippedNativeLibsDir "$buildDir/ndklibs/libs"
                }
    
                proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
    
                ndk.debugSymbolLevel = "FULL" // Generate native debug symbols
            }
        }
    
        packagingOptions {
            exclude 'META-INF/DEPENDENCIES'
            exclude 'META-INF/rxjava.properties'
        }
    
        kotlinOptions {
            jvmTarget = '1.8'
        }
    
        testOptions {
            unitTests {
                includeAndroidResources = true
            }
        }
    
        android.buildFeatures.viewBinding = true
    
      dependencies {
     // Testing-only dependencies
            testImplementation 'junit:junit:4.13.2'
    
            // Core library
            androidTestImplementation 'androidx.test:core:1.4.0'
    
            // AndroidJUnitRunner and JUnit Rules
            androidTestImplementation 'androidx.test:runner:1.4.0'
            androidTestImplementation 'androidx.test:rules:1.4.0'
    
            // Assertions
            androidTestImplementation 'androidx.test.ext:junit:1.1.3'
            androidTestImplementation 'androidx.test.ext:truth:1.4.0'
            
            // Espresso dependencies
            androidTestImplementation "androidx.test.espresso:espresso-core:$espresso_version"
            androidTestImplementation "androidx.test.espresso:espresso-intents:$espresso_version"
    androidTestImplementation "androidx.test.espresso:espresso-web:$espresso_version"
            androidTestImplementation "androidx.test.espresso.idling:idling-concurrent:$espresso_version"
    
            // The following Espresso dependency can be either "implementation"
            // or "androidTestImplementation", depending on whether you want the
            // dependency to appear on your APK's compile classpath or the test APK
            // classpath.
            androidTestImplementation "androidx.test.espresso:espresso-idling-resource:$espresso_version"
    
      //Dagger
            implementation "com.google.dagger:dagger:$dagger_version"
            kapt "com.google.dagger:dagger-compiler:$dagger_version"
    
            // region Hilt
            implementation "com.google.dagger:hilt-android:$hilt_version"
            implementation "androidx.hilt:hilt-navigation-fragment:$hilt_fragment_version"
            kapt "com.google.dagger:hilt-android-compiler:$hilt_version" // or :  kapt 'com.google.dagger:hilt-compiler:2.37'
    
            // Testing Navigation
            androidTestImplementation("androidx.navigation:navigation-testing:$nav_version")
    
            // region Hilt testing - for instrumentation tests
            androidTestImplementation "com.google.dagger:hilt-android-testing:$hilt_version"
            // Make Hilt generate code in the androidTest folder
            kaptAndroidTest "com.google.dagger:hilt-android-compiler:$hilt_version"
            // endregion
    
            // For local unit tests
            testImplementation 'com.google.dagger:hilt-android-testing:2.37'
            kaptTest 'com.google.dagger:hilt-compiler:2.37'
    
            androidTestImplementation 'com.google.dagger:hilt-android-testing:2.37'
      }
    }
    
    
    kapt {
        correctErrorTypes true
        javacOptions {
            // These options are normally set automatically via the Hilt Gradle plugin, but we
            // set them manually to workaround a bug in the Kotlin 1.5.20: https://github.com/google/dagger/issues/2684
            option("-Adagger.fastInit=ENABLED")
            option("-Adagger.hilt.android.internal.disableAndroidSuperclassValidation=true")
        }
    }
    
    // https://github.com/google/protobuf-gradle-plugin
    protobuf {
        protoc {
            artifact = "com.google.protobuf:protoc:$protobuf_version"
            //    path = localProperties["protoc.dir"]
        }
    
        // Generates the java Protobuf-lite code for the Protobufs in this project. See
        // https://github.com/google/protobuf-gradle-plugin#customizing-protobuf-compilation
        // for more information.
        generateProtoTasks {
            all().each { task ->
                task.builtins {
                    java {
                        option 'lite'
                    }
                }
            }
        }
    }
    
    dependencies {
        implementation 'androidx.legacy:legacy-support-v4:1.0.0'
        implementation 'androidx.appcompat:appcompat:1.3.1'
        implementation 'com.google.android.material:material:1.4.0'
        implementation 'androidx.constraintlayout:constraintlayout:2.1.0'
    }

Run Code Online (Sandbox Code Playgroud)

(上面我只添加了用于测试的依赖项)项目gradle

// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
    ext.kotlin_version = "1.5.20"
    ext.google_version = '4.3.4'
    ext.timberVersion = '4.7.1'
    ext.event_bus = '3.2.0'
    ext.gson_version = '2.8.6'
    ext.retrofit2_version = '2.9.0'
    ext.datastore_version = '1.0.0-rc02'
    ext.rxkotlin_version = '3.0.1'
    ext.rxandroid_version = '3.0.0'
    ext.lifecycle_version = '2.3.1'
    ext.dagger_version = '2.37'
    ext.hilt_version = '2.37'
    ext.hilt_fragment_version = '1.0.0'
    ext.nav_version = '2.3.5'
    ext.fragment_version = '1.3.6'
    ext.androidXTestCoreVersion = '1.4.0'
    ext.espresso_version = '3.4.0'
    ext.lottie_version = '3.6.1'
    ext.facebook_version = '9.0.0'
    ext.protobuf_version = '3.15.8'
    ext.protobuf_gradle_plugin_version = '0.8.16'

    repositories {
        google()
        mavenCentral()
    }
    dependencies {
        classpath "com.android.tools.build:gradle:4.2.2"
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath "com.google.gms:google-services:$google_version"
        classpath 'com.google.firebase:firebase-crashlytics-gradle:2.7.1'
        classpath 'com.google.firebase:perf-plugin:1.4.0'
        classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
        classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
        classpath "com.google.protobuf:protobuf-gradle-plugin:$protobuf_gradle_plugin_version"

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        google()
        mavenCentral()
        jcenter() // Warning: this repository is going to shut down soon
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}
Run Code Online (Sandbox Code Playgroud)

我按照此页面中 dagger-hilt 的官方文档进行操作:https://dagger.dev/hilt/testing.html,但我仍然收到此错误:


         Caused by: java.lang.ClassCastException: com.crowdpolicy.onext.igme.HiltTestApplication cannot be cast to android.app.Application
     at android.app.Instrumentation.newApplication(Instrumentation.java:997)
            at android.app.Instrumentation.newApplication(Instrumentation.java:982)
            at com.crowdpolicy.onext.igme.HiltTestRunner.newApplication(HiltTestRunner.kt:14)
            at android.app.LoadedApk.makeApplication(LoadedApk.java:617)

Run Code Online (Sandbox Code Playgroud)

我不知道如何解决它,因为我对测试真的很陌生,这是第一次面对它!HIltTestRunner 中的第 14 行是: return super.newApplication(cl,HiltTestApp::class.java.name, context)

小智 7

我遇到了同样的问题,在测试时我得到了ClassCastException: HiltTestApplication cannot be cast to AbcApp(我的应用程序类)。

解决方案是@CustomTestApplication注解,参见Dagger DocsAndroid Dev Docs以及生成的类的使用

<interfacename>_Application.
Run Code Online (Sandbox Code Playgroud)

进一步请注意,当使用注释时,仅使用 IgmeApplication(或在我的例子中为 AbcApp)是不可能的@HiltAndroidApp,请参阅open Issue。然后你必须open class AbstractApplication: Application()像提问者那样做。然后由您的应用程序(在我的情况下为 AbcApp 或在提问者的情况下为 IgmeApplication)进行子类化,并由注释 @CustomTestApplication 创建的类进行子类化。喜欢:

@CustomTestApplication(AbstractApplication::class)
interface CustomTestApplicationForHilt
Run Code Online (Sandbox Code Playgroud)

请注意,interface使用 代替open class,但它也适用于公开课,就像提问者所做的那样。

CustomTestApplicationForHilt_Application然后,在 Testrunner 类中调用 newApplication 时,必须使用创建的类,例如:

 class HiltTestRunner : AndroidJUnitRunner() {
    override fun newApplication(cl: ClassLoader?, className: String?, context: Context?): Application {
        return super.newApplication(cl, CustomTestApplicationForHilt_Application::class.java.name, context)
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,您可以根据需要选择 @CustomTestApplication 注释(此处为 CustomTestApplicationForHilt)的接口名称,但_Application在构建应用程序时处理注释时,结尾将添加到类名称中。另请注意,该类在构建之前不可用。