#android #android-studio #testing #dagger-hilt
Вопрос:
Я уже довольно давно пишу код kotlin для приложений Android, но решил также начать писать код тестирования для своих приложений. Однако у меня возникли некоторые проблемы с использованием рукояти. То, что я пытался, это :
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() {
}
}
my app level gradle file :
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'
}
(Above I added only the dependencies used for testing )
project 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
}
Я следовал официальной документации от рукояти кинжала на этой странице : 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)
и я не знаю, как это исправить, потому что я действительно новичок в тестировании и сталкиваюсь с этим впервые !
строка 14 в HIltTestRunner-это :
вернуть супер.новое приложение(cl,HiltTestApp::class.java.name, контекст)
Комментарии:
1. нашел решение в github Google
Ответ №1:
У меня была та же проблема, во время тестирования я получил ClassCastException: HiltTestApplication cannot be cast to AbcApp
(мой класс приложений).
Решение заключается в @CustomTestApplication
аннотации, см. Документы Dagger или Документы разработчиков Android и использование созданного класса
<interfacename>_Application.
Далее имейте в виду, что простое использование IgmeApplication (или AbcApp в моем случае) невозможно при использовании @HiltAndroidApp
аннотации, см. раздел «Открытая проблема«. Тогда вы должны сделать open class AbstractApplication: Application()
то же, что сделал спрашивающий. Который затем подразделяется вашим приложением (AbcApp в моем случае или IgmeApplication в случае с вопросами) и подразделяется созданным классом из аннотации @CustomTestApplication. Нравится:
@CustomTestApplication(AbstractApplication::class)
interface CustomTestApplicationForHilt
Обратите внимание , что interface
используется вместо open class
, но он также работает с открытым классом, как это сделал спрашивающий.
Созданный класс затем CustomTestApplicationForHilt_Application
должен использоваться при вызове newApplication в вашем классе Testrunner, например:
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
будет добавлено к имени класса, когда аннотация будет обработана во время создания приложения. Также обратите внимание, что класс не будет доступен до сборки.