使用自定义Interceptor时Espresso不等待Mock响应的问题
首先,你的问题根源有两个:FakeInterceptor根本没被实际调用,以及Espresso无法感知RxJava异步任务的完成状态。我们一步步来拆解解决:
1. 修复Repository的Mock配置问题
你当前的Mockito用法存在逻辑误区:你用@Mock创建了一个空的userRepository mock对象,然后试图让它返回mockUserRepository.getUserInfo()的结果,但实际上当测试代码调用userRepository.getUserInfo()时,Mockito会直接返回预设的mock值,根本不会走到mockUserRepository的真实逻辑,自然不会触发FakeInterceptor的intercept方法。
正确的做法是替换掉App中依赖的真实Repository实例,而不是用Mockito mock它。比如通过依赖注入框架或者手动替换:
@Before public void setUp() { appContext = InstrumentationRegistry.getTargetContext(); // 1. 创建带FakeInterceptor的OkHttpClient OkHttpClient okHttpClient = new OkHttpClient.Builder() .addInterceptor(new FakeInterceptor()) .build(); // 2. 构建带FakeInterceptor的Retrofit实例 Retrofit retrofit = new Retrofit.Builder() .addConverterFactory(GsonConverterFactory.create(JsonUtils.getInstance().getGson())) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .baseUrl(Constants.END_POINT) .client(okHttpClient) .build(); // 3. 创建真实的UserRepository实例,使用上面的Retrofit UserRepository mockUserRepository = new UserRepositoryImpl(retrofit); // 替换为你的实际Repository实现类 // 4. 替换App中的Repository依赖(假设Injection类提供静态替换方法) Injection.setUserRepository(mockUserRepository); }
这样当Fragment调用getUserInfo()时,会实际走到带FakeInterceptor的Retrofit请求,触发拦截器逻辑。
2. 让Espresso等待RxJava异步任务完成
Espresso默认只会等待主线程的任务完成,但Retrofit搭配RxJava2CallAdapterFactory时,网络请求是在IO线程执行的,Espresso无法感知这些异步任务。你需要让Espresso能监听RxJava的任务状态,这里有两种实用方案:
方案A:使用RxIdler(推荐)
RxIdler是官方生态的库,能自动将RxJava的Scheduler包装成Espresso可识别的IdlingResource,让Espresso自动等待所有Rx任务完成。
首先添加依赖(在模块的build.gradle的androidTestImplementation块中):
androidTestImplementation 'com.jakewharton.rxidler:rxidler:0.10.0' androidTestImplementation 'com.jakewharton.rx2idler:rx2idler:0.10.0'
然后在测试的setUp方法中替换RxJava的默认Scheduler:
@Before public void setUp() { // ... 其他初始化代码 // 替换RxJava的IO和Computation Scheduler为Idler版本 RxJavaPlugins.setIoSchedulerHandler(Rx2Idler.create("RxJava IO Scheduler")); RxJavaPlugins.setComputationSchedulerHandler(Rx2Idler.create("RxJava Computation Scheduler")); }
方案B:测试中强制RxJava在主线程执行
如果不想引入额外库,可以在测试中让RxJava的所有任务都在主线程执行,这样Espresso就能直接感知并等待:
@Before public void setUp() { // ... 其他初始化代码 RxJavaPlugins.setIoSchedulerHandler(scheduler -> Schedulers.trampoline()); RxJavaPlugins.setComputationSchedulerHandler(scheduler -> Schedulers.trampoline()); }
3. (可选)给FakeInterceptor添加延迟模拟真实网络
真实API存在网络延迟,而你的FakeInterceptor是同步立即返回的,可能导致Espresso误以为数据已加载完成(主线程无任务),直接执行下一次点击。可以在拦截器中添加短暂延迟,模拟真实网络情况:
@Override public Response intercept(Chain chain) throws IOException { Response response; String responseString = ""; String requestedPath = chain.request().url().encodedPath(); if (requestedPath.equalsIgnoreCase("api/users/profile/recommendsTab")) { responseString = PROFILE__USER_RECOMMENDS; } else if (requestedPath.equalsIgnoreCase("api/users/me")) { responseString = PROFILE__USER; } // 添加500ms延迟,模拟网络请求耗时 try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } response = new Response.Builder() .code(200) .message(responseString) .request(chain.request()) .protocol(Protocol.HTTP_1_0) .body(ResponseBody.create(MediaType.parse("application/json"), responseString.getBytes())) .addHeader("content-type", "application/json") .build(); return response; }
4. 调整Espresso测试代码,明确等待UI状态
最后,建议在每次点击后添加UI检查,确保数据加载完成后再执行下一步操作:
// 点击按钮1后,等待推荐列表加载完成 onView(withId(R.id.button_1)).perform(click()); onView(withId(R.id.recycler_recommends)).check(matches(isDisplayed())); // 点击按钮2后,等待用户信息UI出现 onView(withId(R.id.button_2)).perform(click()); onView(withId(R.id.text_user_name)).check(matches(isDisplayed())); // 最后点击按钮3 onView(withId(R.id.button_3)).perform(click());
这样Espresso会严格等待指定UI元素加载完成后,再执行后续操作,彻底避免连续点击跳过数据加载的问题。
内容的提问来源于stack exchange,提问作者Tran Hoai Nam

