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

Hilt测试报错:@TestInstallIn#replaces()仅支持@InstallIn标注模块

Hilt测试模块替换依赖报错解决

错误场景

为Instrumentation测试用TestCase模块提供Mock对象时,触发Hilt错误,错误信息如下:

[Hilt]
public final class TestModules {
^
@TestInstallIn#replaces() can only contain @InstallIn modules, but found: [com.davidozersky.posterpal.repo.UserRepository]

项目相关代码

主源码集的AppModule(提供真实依赖)

@Module
@InstallIn(SingletonComponent::class)
object AppModule {

    @Provides
    @Singleton
    fun provideJobApi(): JobsRetroInterface {
        val interceptor: Interceptor = object : Interceptor {
            @Throws(IOException::class)
            override fun intercept(chain: Interceptor.Chain): Response {
                var request: Request = chain.request()
                val builder: Request.Builder =
                    request.newBuilder().addHeader("Cache-Control", "no-cache")
                request = builder.build()
                return chain.proceed(request)
            }
        }
        val client = OkHttpClient.Builder()
            .addInterceptor(interceptor)
            .cache(null)
            .build()
        val retrofit = Retrofit.Builder()
            .baseUrl(Constants.BASE_URL)
            .addConverterFactory(MoshiConverterFactory.create())
            .client(client)
            .build()
        return retrofit.create(JobsRetroInterface::class.java)
    }

    @Provides
    @Singleton
    fun provideJobDao(database : JobDatabase) : JobDao {
        return database.jobDao
    }

    @Provides
    @Singleton
    fun provideDatabase(@ApplicationContext application : Context) : JobDatabase {
        return Room.databaseBuilder(application,
                JobDatabase::class.java,
                JobDatabase.Companion.NAME).fallbackToDestructiveMigration().build()
    }

    @Provides
    @Singleton
    fun provideFireBaseAuth(): FirebaseAuth {
        return FirebaseAuth.getInstance()
    }

    @Provides
    @Singleton
    fun provideUserRepository() : UserRepository {
        return UserRepositoryImpl(FirebaseAuth.getInstance())
    }
}

测试用的TestModules(尝试替换为Mock对象)

package com.davidozersky.posterpal

import android.app.Activity
import com.davidozersky.posterpal.api.JobsRetroInterface
import com.davidozersky.posterpal.database.JobDao
import com.davidozersky.posterpal.database.entities.*
import com.davidozersky.posterpal.database.relations.OwnerWithJobs
import com.davidozersky.posterpal.database.relations.jobworkerposter.JobPW
import com.davidozersky.posterpal.database.relations.posterworker.WorkerWithPosters
import com.davidozersky.posterpal.repo.JobsRepository
import com.davidozersky.posterpal.repo.UserRepository
import com.davidozersky.posterpal.repo.UserRepositoryImpl
import com.davidozersky.posterpal.ui.fragments.SignUpFragment
import dagger.Binds
import dagger.Module
import dagger.Provides
import dagger.hilt.android.components.FragmentComponent
import dagger.hilt.components.SingletonComponent
import dagger.hilt.testing.TestInstallIn
import io.mockk.mockk
import kotlinx.coroutines.flow.Flow
import retrofit2.Response
import javax.inject.Inject
import javax.inject.Singleton

@Module
@TestInstallIn(
    components = [SingletonComponent::class],
    replaces = [UserRepository::class]
)
object TestModules {

    @Provides
    @Singleton
    fun provideUserService() : UserRepository {
        return FakeUserRepo()
    }

    class FakeJobsRepo(override var appliedAndAcceptedJobs: List<JobPW>) : JobsRepository {
        override fun getDao(): JobDao {
            TODO("Not yet implemented")
        }

        override fun getApi(): JobsRetroInterface {
            TODO("Not yet implemented")
        }

        override suspend fun getAllJobs(): List<JobPW> {
            TODO("Not yet implemented")
        }

        override fun getAllJobsFlow(): Flow<List<JobPW>> {
            TODO("Not yet implemented")
        }

        override suspend fun getJobById(id: Int): JobPW? {
            TODO("Not yet implemented")
        }

        override suspend fun insertJob(job: Job) {
            TODO("Not yet implemented")
        }

        override suspend fun getOwner(id: Int): OwnerWithJobs {
            TODO("Not yet implemented")
        }

        override suspend fun insertOwner(owner: Owner): Long {
            TODO("Not yet implemented")
        }

        override suspend fun getPoster(id: Int): Poster? {
            TODO("Not yet implemented")
        }

        override suspend fun insertPoster(poster: Poster) {
            TODO("Not yet implemented")
        }

        override suspend fun getWorkerWithPoster(worker_id: Long): Flow<WorkerWithPosters> {
            TODO("Not yet implemented")
        }

        override suspend fun getJobsByAPI(): Response<List<JobPW>>? {
            TODO("Not yet implemented")
        }

        override suspend fun addWorkersToJob() {
            TODO("Not yet implemented")
        }

        override suspend fun addPostersToJob() {
            TODO("Not yet implemented")
        }

        override suspend fun saveJobToDatabase(job: JobPW): Long {
            TODO("Not yet implemented")
        }

        override suspend fun addWorkerToPoster(poster_id: Long, worker: Worker) {
            TODO("Not yet implemented")
        }
    }

    class FakeUserRepo : UserRepository {
        suspend fun getUsers() = mockk<List<User>>()

        override fun checkForUser() {
            TODO("Not yet implemented")
        }

        override fun signUpWithEmailAndPassword(
            email: String?,
            password: String?,
            activity: Activity,
            signUpListener: SignUpFragment.SignUpListener
        ) {
            TODO("Not yet implemented")
        }
    }
}

错误原因

报错核心是**@TestInstallIn的replaces参数只能接收带有@InstallIn注解的模块类**,而你传入的UserRepository是业务接口,不是Hilt模块,不符合要求。

解决方案

方案1:拆分模块(推荐)

将UserRepository的提供逻辑从AppModule中拆分出来,单独创建UserModule:

// 主源码集新建UserModule.kt
@Module
@InstallIn(SingletonComponent::class)
object UserModule {
    @Provides
    @Singleton
    fun provideUserRepository() : UserRepository {
        return UserRepositoryImpl(FirebaseAuth.getInstance())
    }
}

修改AppModule,删除原来的provideUserRepository方法。

最后修改测试模块的@TestInstallIn注解,替换目标为UserModule:

@Module
@TestInstallIn(
    components = [SingletonComponent::class],
    replaces = [UserModule::class] // 替换为拆分后的模块
)
object TestModules {
    // ... 原有代码不变
}

方案2:卸载原模块并重新提供所有依赖

如果不想拆分模块,可以在测试类上使用@UninstallModules(AppModule::class)卸载整个AppModule,然后在TestModules中提供所有需要的依赖(包括原AppModule中的其他依赖,可用Mock替代):

  1. 测试类添加注解:
@HiltAndroidTest
@UninstallModules(AppModule::class)
class YourInstrumentationTest {
    // ... 测试代码
}
  1. 修改TestModules,补充原AppModule中的其他依赖提供(示例用Mock替代):
@Module
@TestInstallIn(components = [SingletonComponent::class])
object TestModules {

    @Provides
    @Singleton
    fun provideUserService() : UserRepository {
        return FakeUserRepo()
    }

    // 补充原AppModule中的其他依赖,这里用mockk示例
    @Provides
    @Singleton
    fun provideJobApi(): JobsRetroInterface = mockk()

    @Provides
    @Singleton
    fun provideJobDao(database : JobDatabase) : JobDao = mockk()

    @Provides
    @Singleton
    fun provideDatabase(@ApplicationContext application : Context) : JobDatabase = mockk()

    @Provides
    @Singleton
    fun provideFireBaseAuth(): FirebaseAuth = mockk()

    // ... 原有Fake类代码不变
}

这样就能解决Hilt报错,成功替换为Mock对象进行测试。

内容的提问来源于stack exchange,提问作者user1743524

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 09:59:58