如何通过Hilt配置Retrofit测试URL以适配MockWebServer
如何在使用Hilt的Android项目中配合MockWebServer动态修改Retrofit的BaseUrl进行测试?
首先,你现有代码里把baseUrl作为object的可变属性,这种方式在测试场景下会有全局状态污染的问题——不同测试用例之间可能互相干扰,也不符合依赖注入的设计原则。我们可以通过Hilt的测试替换机制优雅解决这个问题,下面分两种方案说明:
方案一:重构原模块+测试模块替换
第一步:优化生产环境的RetrofitModule
先把硬编码的baseUrl抽成单独的依赖提供方法,让Hilt来管理它,这样测试时就能轻松替换:
@InstallIn(ApplicationComponent::class) object RetrofitModule { // 将BaseUrl改为通过@Provides提供 @Singleton @Provides fun provideBaseUrl(): String { return "https://my.fancy.api" } @Singleton @Provides fun providesRetrofitClient( baseUrl: String, // 注入BaseUrl依赖 okHttpClient: OkHttpClient ): Retrofit { return Retrofit.Builder() .baseUrl(baseUrl) .addConverterFactory(GsonConverterFactory.create()) .client(okHttpClient) .build() } // OkHttpClient和Service的提供方法保持不变 @Singleton @Provides fun providesOkHttpClient(): OkHttpClient { val okHttpClientBuilder = OkHttpClient.Builder() val loggingInterceptor = HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY } okHttpClientBuilder.addInterceptor(loggingInterceptor) return okHttpClientBuilder.build() } @Singleton @Provides fun providesJokeGeneratorService(retrofit: Retrofit): FancyApiService { return retrofit.create(FancyApiService::class.java) } }
第二步:创建测试专用的Retrofit模块
用@TestInstallIn注解创建测试模块,替换生产环境的RetrofitModule,并提供MockWebServer的BaseUrl:
@Module @TestInstallIn( components = [ApplicationComponent::class], replaces = [RetrofitModule::class] ) object TestRetrofitModule { // 注入MockWebServer,返回它的BaseUrl @Singleton @Provides fun provideBaseUrl(mockWebServer: MockWebServer): String { return mockWebServer.url("/").toString() } // 可复用生产环境的OkHttpClient配置,也可根据测试需求调整 @Singleton @Provides fun providesOkHttpClient(): OkHttpClient { val okHttpClientBuilder = OkHttpClient.Builder() val loggingInterceptor = HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY } okHttpClientBuilder.addInterceptor(loggingInterceptor) return okHttpClientBuilder.build() } @Singleton @Provides fun providesJokeGeneratorService(retrofit: Retrofit): FancyApiService { return retrofit.create(FancyApiService::class.java) } }
第三步:编写测试类
在测试类中初始化Hilt和MockWebServer,注入需要测试的API服务:
@HiltAndroidTest class FancyApiRepositoryTest { @get:Rule val hiltRule = HiltAndroidRule(this) @Inject lateinit var mockWebServer: MockWebServer @Inject lateinit var fancyApiService: FancyApiService @Before fun setUp() { hiltRule.inject() // 启动MockWebServer mockWebServer.start() } @After fun tearDown() { // 测试结束后关闭MockWebServer mockWebServer.shutdown() } @Test fun `test fetch fancy data`() = runTest { // 配置MockWebServer的模拟响应 mockWebServer.enqueue(MockResponse() .setBody("""{"data": "test content"}""") .setResponseCode(200)) // 调用API服务 val response = fancyApiService.fetchData() // 断言结果符合预期 assertEquals("test content", response.data) } }
方案二:用@BindValue快速替换BaseUrl(更轻量)
如果你不想编写完整的测试模块,可以直接在测试类中用@BindValue注解提供测试用的BaseUrl,前提是你已经完成了方案一的第一步重构:
@HiltAndroidTest class FancyApiRepositoryTest { @get:Rule val hiltRule = HiltAndroidRule(this) @Inject lateinit var mockWebServer: MockWebServer // 绑定测试用的BaseUrl,自动替换生产环境的provideBaseUrl @BindValue val testBaseUrl: String by lazy { mockWebServer.url("/").toString() } @Inject lateinit var fancyApiService: FancyApiService @Before fun setUp() { hiltRule.inject() mockWebServer.start() } @After fun tearDown() { mockWebServer.shutdown() } // 测试方法同上... }
这种方式适合只需要替换单个依赖的场景,代码更简洁。
为什么不推荐直接修改object的var?
直接修改RetrofitModule.baseUrl会导致全局状态被修改,比如A测试用例改了BaseUrl后,B测试用例如果没还原,就会用错误的地址测试,引发不可预期的问题。而用Hilt的测试替换机制,每个测试用例都是隔离的,不会互相干扰。
内容的提问来源于stack exchange,提问作者dudi
相关产品推荐
相关产品推荐

