使用Hilt注入Room测试数据库时出现NullPointerException问题
Hilt注入Room数据库测试时出现NullPointerException(Null element)
问题背景
原本Room数据库单元测试运行正常,改为通过Hilt的TestAppModule注入数据库构建器,同时配置了自定义HiltTestRunner到Gradle的testInstrumentationRunner后,运行测试时出现Hilt的Null element空指针错误。
完整错误信息
error: [Hilt] Null element: java.lang.NullPointerException: Null element at dagger.hilt.processor.internal.root.AutoValue_Root.<init>(AutoValue_Root.java:19) at dagger.hilt.processor.internal.root.Root.createDefaultRoot(Root.java:46) at [Hilt] Processing did not complete. See error above for details. 1 error
相关代码
HiltTestRunner.kt
class HiltTestRunner:AndroidJUnitRunner() { override fun newApplication( cl: ClassLoader?, className: String?, context: Context? ): Application { return super.newApplication(cl, HiltTestApplication::class.java.name, context) } }
NewsArticleDaoTest.kt
@RunWith(AndroidJUnit4::class) @SmallTest @HiltAndroidTest class NewsArticleDaoTest{ @get:Rule var hiltRule = HiltAndroidRule(this) @get:Rule var instantTaskExecutorRule = InstantTaskExecutorRule() @Inject @Named("test_db") lateinit var database: NewsArticleDatabase private lateinit var dao: NewsArticleDao @Before fun setup(){ hiltRule.inject() dao = database.newsArticleDao() dao = database.newsArticleDao() } @After fun teardown(){ database.close() } @Test fun insertNewsArticle() = runTest { val testArticle = NewsArticle("Time traveller shares footage from three weeks in the future showing who wins the World Cup","https://www.ladbible.com/sport/time-traveller-who-wins-world-cup-2022-20221128","https://images.ladbible.com/resize?type=webp&quality=70&width=671&fit=contain&gravity=null&dpr=2&url=https://eu-images.contentstack.com/v3/assets/bltcd74acc1d0a99f3a/bltb6064933a4b82b9c/6384c84adb8e364b186bfb6c/Most_prolific_speed_camera_in_the_UK_has_caught_almost_50_000_drivers_this_year_(42).png") val testItem = listOf(testArticle) dao.insertArticles(testItem) val testFeed = NewsFeed(testArticle.url) val feedTestItem = listOf(testFeed) dao.insertNewsFeed(feedTestItem) val allNewsArticles = dao.getAllNewsArticles().first() assertThat(allNewsArticles).contains(testArticle) } @Test fun deleteAllArticles()= runTest { val testArticle = NewsArticle("Time traveller claims discovery of mysterious sea creature will change world","https://www.dailystar.co.uk/news/weird-news/time-traveller-claims-discovery-mysterious-28766022","https://i2-prod.dailystar.co.uk/incoming/article28766081.ece/ALTERNATES/s615b/1_A-SELF-proclaimed-time-traveller-from-2198-claims-experts-will-soon-make-a-chilling-ocean-discovery.jpg") val testItem = listOf(testArticle) dao.insertArticles(testItem) val testFeed = NewsFeed(testArticle.url) val feedTestItem = listOf(testFeed) dao.insertNewsFeed(feedTestItem) dao.deleteAllNewsFeed() val allArticles = dao.getAllNewsArticles().first() assertThat(allArticles).doesNotContain(testArticle) } }
TestAppModule.kt
@Module @InstallIn(SingletonComponent::class) object TestAppModule { @Provides @Named("test_db") fun provideInMemoryDb(@ApplicationContext context: Context) = Room.inMemoryDatabaseBuilder(context,NewsArticleDatabase::class.java ) .allowMainThreadQueries() .build() }
Gradle配置
应用级build.gradle
plugins { id 'com.android.application' id 'kotlin-android' id 'kotlin-kapt' id 'dagger.hilt.android.plugin' id 'kotlin-parcelize' id 'androidx.navigation.safeargs' } android { compileSdkVersion 33 buildToolsVersion "30.0.3" defaultConfig { applicationId "com.rimapps.wisetest" minSdkVersion 21 targetSdkVersion 33 versionCode 1 versionName "1.0" testInstrumentationRunner "com.rimapps.wisetest.HiltTestRunner" buildConfigField("String", "NEWS_API_ACCESS_KEY", news_api_access_key) } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } buildFeatures { viewBinding true } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } kotlinOptions { jvmTarget = '1.8' freeCompilerArgs += "-Xopt-in=androidx.paging.ExperimentalPagingApi" freeCompilerArgs += "-Xopt-in=kotlinx.coroutines.ExperimentalCoroutinesApi" } } dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation 'androidx.core:core-ktx:1.9.0' implementation 'androidx.appcompat:appcompat:1.5.1' implementation 'com.google.android.material:material:1.6.1' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.navigation:navigation-fragment-ktx:2.5.3' implementation 'androidx.navigation:navigation-ui-ktx:2.5.3' testImplementation 'org.junit.jupiter:junit-jupiter' androidTestImplementation 'androidx.test.ext:junit:1.1.4' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0' // Glide implementation "com.github.bumptech.glide:glide:4.14.2" // Dagger Hilt implementation "com.google.dagger:hilt-android:2.44.2" kapt "com.google.dagger:hilt-android-compiler:2.44.2" // Coroutines implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4' // Retrofit + GSON implementation "com.squareup.retrofit2:retrofit:2.9.0" implementation "com.squareup.retrofit2:converter-gson:2.9.0" // Room implementation "androidx.room:room-runtime:2.5.0-rc01" kapt "androidx.room:room-compiler:2.5.0-rc01" implementation "androidx.room:room-ktx:2.5.0-rc01" // SwipeRefreshLayout implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" // Paging 3 implementation "androidx.paging:paging-runtime-ktx:3.2.0-alpha03" // Fragment implementation 'androidx.fragment:fragment-ktx:1.6.0-alpha04' // Local Unit Tests implementation "androidx.test:core:1.5.0" testImplementation "junit:junit:4.13.2" testImplementation "org.hamcrest:hamcrest-all:1.3" testImplementation "androidx.arch.core:core-testing:2.1.0" testImplementation "org.robolectric:robolectric:4.3.1" testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4" testImplementation "com.google.truth:truth:1.0.1" testImplementation "org.mockito:mockito-core:3.4.6" // Instrumented Unit Tests androidTestImplementation "junit:junit:4.13.2" androidTestImplementation "org.mockito:mockito-android:2.25.0" androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4" androidTestImplementation "androidx.arch.core:core-testing:2.1.0" androidTestImplementation "com.google.truth:truth:1.0.1" androidTestImplementation 'androidx.test.ext:junit:1.1.4' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0' androidTestImplementation "org.mockito:mockito-core:3.4.6" androidTestImplementation 'com.google.dagger:hilt-android-testing:2.28-alpha' kaptAndroidTest 'com.google.dagger:hilt-android-compiler:2.44.2' debugImplementation "androidx.fragment:fragment-testing:1.5.5" } kapt { correctErrorTypes true }
项目级build.gradle
buildscript { ext.kotlin_version = "1.7.20" repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.3.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.6.0-alpha04" classpath "com.google.dagger:hilt-android-gradle-plugin:2.44.2" } } allprojects { repositories { google() mavenCentral() } } task clean(type: Delete) { delete rootProject.buildDir }
解决方案
1. 统一Hilt测试依赖版本
当前hilt-android-testing版本(2.28-alpha)与主Hilt版本(2.44.2)不一致,这是核心问题。修改应用级Gradle依赖:
androidTestImplementation 'com.google.dagger:hilt-android-testing:2.44.2'
2. 补充HiltTestApplication类
自定义TestRunner指定了HiltTestApplication,但代码中未实现该类,需补充:
@HiltAndroidApp class HiltTestApplication : Application()
3. 移除重复的运行器注解
@HiltAndroidTest已内置JUnit4运行器,无需额外添加@RunWith(AndroidJUnit4::class),删除该注解:
// @RunWith(AndroidJUnit4::class) 移除此行 @SmallTest @HiltAndroidTest class NewsArticleDaoTest{ // 其余代码不变 }
4. 调整TestAppModule的安装组件
测试模块建议使用TestSingletonComponent而非SingletonComponent,提升测试隔离性:
@Module @InstallIn(TestSingletonComponent::class) // 修改为此组件 object TestAppModule { // 其余代码不变 }
5. 清理并重新构建项目
执行Gradle的clean和build任务,清除编译缓存,避免旧代码干扰。
内容的提问来源于stack exchange,提问作者Mohamed Rimshad
相关产品推荐
相关产品推荐

