Robolectric 和 Retrofit - 等待响应

Robolectric and Retrofit - wait for response

我想测试我的代码是否正确地从 API 下载数据(使用 Retrofit)并将它们显示在 RecyclerView 中。为了做到这一点,我创建了一个模拟 API 的拦截器(基于 this solution)并创建了一个测试(使用 Robolectric):

    @Test
    public void listLoadedCorrectlyTest() {
        MyListFragment myListFragment = new MyListFragment();
        Bundle args = new Bundle();
        FragmentTestUtil.startFragment(myListFragment);
        Robolectric.flushForegroundThreadScheduler();
        JSONArray responseArray = new JSONArray();
        try {
            responseArray = new JSONArray(ApiInterceptor.MOCK_RESPONSE);
        } catch (JSONException e) {
            Log.e("JSON", e.getMessage());
        }
        int adapterItemCount = ((RecyclerView) myListFragment.getView()
                .findViewById(R.id.rv_fruits)).getAdapter().getItemCount();
        assertThat(adapterItemCount).isEqualTo(responseArray.length());
    }

问题是测试有时通过有时失败。我调试了代码,我知道这是因为 MyListFragment 中的数据使用 Retrofit 的 enqueue() 方法加载到 rv_fruits RecyclerView 中。如何等待 Retrofit 回调,以便在数据加载到 RecyclerView 后检查断言?

Robolectric 尝试使所有执行同步,因为异步代码会导致不稳定的测试。如果您使用像 AsynTask 这样的标准东西,但改造不会这样做,这会很好地工作。

一种方法是隐藏一些在新线程中执行请求的 Retrofit(或 OkHttp)类,而不是直接执行它们。例如 ShadowAsyncTask 在主线程中执行所有可运行的,而不是使用新线程,这是原始实现的方式。

另一种方法是与 Espresso 一起使用的 IdlingResource 概念。每当您启动请求时,然后在单例对象上递增计数器,并在请求完成时递减计数器。在您的测试中,您可以等到计数器为零。

简单的方法,但不好的做法就是简单的睡眠。

我发现将 Robolectric 与 Retrofit(带回调)一起使用时存在主要问题。但是,当您例如设置一个 RestAdapter 和一个 RestAdapter.Builder 你可以有条件地修改它(注意永远不要在你的实际应用程序中使用这个配置):

if(singleThreaded){
    Executor executor = Executors.newSingleThreadExecutor();
    restAdapterBuilder.setExecutors(executor, executor);
}

通过为请求和回调添加这个单线程执行器,所有测试异步代码的标准方法(例如使用 CountDownLatch)都将起作用。

另一种方法:

我一直在使用 Awaitility 工具并取得了巨大的成功。它本质上与闩锁相同,但可读性更高。

你的情况:

@Test
public void listLoadedCorrectlyTest() {
    MyListFragment myListFragment = new MyListFragment();
    Bundle args = new Bundle();
    FragmentTestUtil.startFragment(myListFragment);
    Robolectric.flushForegroundThreadScheduler();
    JSONArray responseArray = new JSONArray();
    try {
        responseArray = new JSONArray(ApiInterceptor.MOCK_RESPONSE);
    } catch (JSONException e) {
        Log.e("JSON", e.getMessage());
    }
    int adapterItemCount = ((RecyclerView) myListFragment.getView()
            .findViewById(R.id.rv_fruits)).getAdapter().getItemCount();
    await().atMost(15, TimeUnit.SECONDS)
                      .until(responseIsSameAsCount(myListFragment, responseArray)
}


    private Callable<Boolean> responseIsSameAsCount(View myListFragment, JSONArray responseArray) {
    return new Callable<Boolean>() {
        @Override
        public Boolean call() throws Exception {
            return  ((RecyclerView) myListFragment.getView()
            .findViewById(R.id.rv_fruits)).getAdapter().getItemCount() == responseArray.length();
        }
    };
}