在Espresso测试中取消或覆写Activity内的API调用
嘿,这个问题我太熟了!在UI测试里碰到这种不可控的API请求确实头疼,尤其是还会触发ViewStub替换界面,直接打乱测试流程。下面给你几个靠谱的方案,帮你把API请求的结果牢牢握在手里:
方案一:依赖注入+测试替身(Test Double)—— 最推荐的长期方案
这是解耦业务逻辑和测试的标准做法,核心是把API调用抽象成接口,然后在测试环境用假实现替换真实的API客户端。
步骤:
- 抽象API服务接口
先把你的API调用逻辑封装成一个接口,比如:interface ApiService { suspend fun fetchRequiredData(): Result<YourDataModel> } - 实现真实和测试用的版本
- 真实环境用
RealApiService,里面写实际的Retrofit/OkHttp调用逻辑 - 测试环境用
FakeApiService,直接返回你预设的成功/失败结果:class FakeApiService(private val presetResult: Result<YourDataModel>) : ApiService { override suspend fun fetchRequiredData(): Result<YourDataModel> { return presetResult } }
- 真实环境用
- 在Activity中注入接口实例
用构造函数注入或者依赖注入框架(比如Hilt、Dagger)把ApiService注入到Activity里,而不是在Activity内部直接创建真实实例:class YourActivity : AppCompatActivity() { private val apiService: ApiService by inject() // 或者构造函数传参 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) lifecycleScope.launch { val result = apiService.fetchRequiredData() if (result.isFailure) { errorViewStub.inflate() } else { // 渲染正常业务界面 } } } } - 测试时替换为Fake实现
比如用Hilt的话,可以通过@TestInstallIn注解在测试模块中替换ApiService的绑定;如果是手动注入,就在启动Activity前把FakeApiService传进去,直接控制返回结果,完全避免真实网络请求。
方案二:用Mockito Mock API客户端—— 快速适配现有代码
如果不想大改现有代码结构,可以直接用Mockito等Mock框架,把Activity里的API客户端实例替换成Mock对象,预设它的返回值。
举个测试代码的例子:
@RunWith(AndroidJUnit4::class) class YourActivityTest { @Mock private lateinit var mockApiService: ApiService @Before fun setup() { MockitoAnnotations.openMocks(this) // 启动Activity前,替换掉里面的apiService实例(这里用反射,也可以结合依赖注入) val activityScenario = launchActivity<YourActivity>() activityScenario.onActivity { activity -> val field = YourActivity::class.java.getDeclaredField("apiService") field.isAccessible = true field.set(activity, mockApiService) } } @Test fun testApiSuccess_DoesNotShowErrorStub() { // 预设API调用返回成功 Mockito.`when`(mockApiService.fetchRequiredData()) .thenReturn(Result.success(YourDataModel())) // 断言错误Stub没有被加载 onView(withId(R.id.error_view_stub)).check(doesNotExist()) } @Test fun testApiFailure_ShowsErrorStub() { // 预设API调用返回失败 Mockito.`when`(mockApiService.fetchRequiredData()) .thenReturn(Result.failure(ApiException())) // 断言错误Stub已被加载 onView(withId(R.id.error_layout)).check(matches(isDisplayed())) } }
⚠️ 注意:如果API调用是在onCreate里立刻执行的,要确保在Activity的onCreate触发前就完成Mock对象的替换,不然可能会用到真实的API客户端。
方案三:用OkHttp拦截器拦截网络请求—— 无侵入式拦截
如果完全不想修改业务代码,可以在测试环境给OkHttp加一个拦截器,直接拦截所有API请求,返回你预设的响应。
实现拦截器:
class MockNetworkInterceptor(private val mockResponse: Response) : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { // 直接返回预设的响应,不走真实网络 return mockResponse.newBuilder() .request(chain.request()) .build() } }
测试时配置Retrofit:
// 构建一个成功的Mock响应 val successResponse = Response.Builder() .code(200) .body(ResponseBody.create(MediaType.parse("application/json"), "{\"key\":\"value\"}")) .build() // 配置带拦截器的OkHttpClient val mockOkHttpClient = OkHttpClient.Builder() .addInterceptor(MockNetworkInterceptor(successResponse)) .build() // 用这个客户端创建Retrofit实例,替换掉真实环境的Retrofit val mockRetrofit = Retrofit.Builder() .baseUrl("https://your-real-base-url.com/") .client(mockOkHttpClient) .build()
这样所有的API请求都会被拦截,返回你预设的结果,不管是成功还是失败。
额外注意点
- 不管用哪种方案,都要确保测试环境不会发起真实网络请求,避免依赖外部环境,保证测试的可重复性。
- 如果用协程执行API调用,测试时要注意等待协程完成:可以用
EspressoIdlingResource标记网络请求状态,或者用Jetpack的lifecycle-testing库来控制协程的执行时机,避免Espresso在协程完成前就进行断言。
内容的提问来源于stack exchange,提问作者Orbit
相关产品推荐
相关产品推荐

