fragon
fragon

Reputation: 3471

Robolectric and Retrofit - wait for response

I would like to test if my code is correctly downloading data from API (using Retrofit) and displays them in RecyclerView. In order to do this I created an interceptor which mocks API (basing on this solution) and created a test (using 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());
    }

The problem is that the test is sometimes passed and sometimes failed. I've debugged the code and I know that this is because the data in MyListFragment are loaded to the rv_fruits RecyclerView using Retrofit's enqueue() method. How can I wait for the Retrofit callback so that the assertion is checked AFTER the data are loaded to the RecyclerView?

Upvotes: 3

Views: 2265

Answers (3)

Vic Vuci
Vic Vuci

Reputation: 7051

Another approach:

I've been using the Awaitility tool with great success. It's essentially the same as a latch, but a lot more readable.

In your case:

@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();
        }
    };
}

Upvotes: 2

James
James

Reputation: 2270

I found major issues with using Robolectric with Retrofit (with callbacks). However, when you are e.g. setting up a RestAdapter with a RestAdapter.Builder you can conditionally modify it (NB never use this configuration in your actual App):

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

By adding this single threaded executor for both request and callback all the standard approaches to testing async code (such as using a CountDownLatch) will then work.

Upvotes: 1

nenick
nenick

Reputation: 7438

Robolectric tries to make all execution synchron because async code leads into flaky tests. This works fine if you use standard stuff like AsynTask but retrofit don't do it.

One approach is to shadow some Retrofit (or OkHttp) classes which execute the request in a new thread and instead execute them directly. For example ShadowAsyncTask execute all runnable in main thread instead of using new thread how it the original implementation would do it.

Another approach is the IdlingResource concept which is used with Espresso. Whenever you start an request then increment a counter on a singleton object and when the request finished decrement the counter. In your tests you can then wait until the counter is zero.

Easy approach but bad practice would be a simple sleep.

Upvotes: 1

Related Questions