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



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


import android.app.Application

open class AbstractApplication: Application()

@HiltAndroidApp
class IgmeApplication : IgmeAbstractApplication() {  

@Inject
lateinit var authenticationManager: AuthenticationManager
....
}

然后在Android测试目录中:


import dagger.hilt.android.testing.CustomTestApplication

@CustomTestApplication(AbstractApplication::class)
open class HiltTestApplication

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)
}
}

我的测试班:


@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() {
}
}

我的应用程序级渐变文件:


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'
}

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

// 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
}

我在这一页中遵循了dagger柄的官方文件: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)

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

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

解决方案是@CustomTestApplication注释,请参阅Dagger Docs或Android Dev Docs和使用生成的类

<interfacename>_Application.

此外,请注意,当使用@HiltAndroidApp注释时,仅使用IgmeApplication(在我的情况下为AbcApp)是不可能的,请参阅open Issue。然后你必须像提问者那样做一个open class AbstractApplication: Application()。然后由您的应用程序(在我的情况下为AbcApp,在提问者的情况下是IgmeApplication)将其子类化,并由annotation@CustomTestApplication创建的类将其子级化。类似:

@CustomTestApplication(AbstractApplication::class)
interface CustomTestApplicationForHilt

请注意,使用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)
}
}

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

最新更新