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();
}
};
}
我想测试我的代码是否正确地从 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();
}
};
}