You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 14:50:27