18

I am writing integration tests that perform actions in the UI which start network calls using Retrofit.

I know I need to implement a CountingIdlingResource, but I want to do it the correct way (and not reinvent the wheel if it has already been done).

Has anyone implemented an IdlingResource in their app's Espresso test suite to wait while network requests execute?

More info here.

Austyn Mahoney
  • 11,398
  • 8
  • 64
  • 85
  • I've never used Retrofit, but from browsing their API, when you do Client.execute inside of an AsyncTask, you don't need an IdlingResource because AsyncTask's thread pool is monitored by Espresso. If for some reason you cannot use AsyncTask, you could still profit from that thread pool monitoring by starting the task via AsyncTask.THREAD_POOL_EXECUTOR.submit(). There are so many (solvable) traps using IdlingResources, that from my experience having as few as possible is best. – haffax Apr 17 '14 at 23:32
  • Retrofit uses it's own Threadpool/Executor, it does not use AsyncTasks. `IdlingResource` will have to be implemented within it, I just wanted to know if someone else already has tried/accomplished this. – Austyn Mahoney Apr 17 '14 at 23:42
  • I have used custom IdlingResources and CountingIdlingResource for various tasks, including network request. Without knowing more about how the UI action initiates the request and how response/cancellation is handled it is hard to give specific advice. Either use the decoration approach from the linked sample, or implement a simple Listener API that the test class can connect the IdlingResource to for knowing when waiting for a network request to finish and when it got finished or aborted. – haffax Apr 18 '14 at 00:02
  • I'm looking for someone who has specifically implemented this with `Retrofit` and its `RestAdapter.Builder().setExecutor(...)` method. It's fine if I don't get an answer, but I will wait for bit before writing my own implementation. – Austyn Mahoney Apr 18 '14 at 00:41
  • @AustynMahoney i'm looking for the exact same thing (recommended approach), any updates on your search? – KG - May 02 '14 at 20:07

5 Answers5

22

The most straightforward solution for this: is to basically swap out Retrofit's Thread-pool executor with an AsyncTask one (as recommended by the very helpful Nick from that linked Google group discussion). I do this like so:

new RestAdapter.Builder()
               .setEndpoint(LOCLSET_SERVER_URL)
               .setExecutors(AsyncTask.THREAD_POOL_EXECUTOR,
                             new MainThreadExecutor())
               .build();

I'm not sure if this is the most appropriate solution, but it's the quickest most sane one that I could get working. Bare in mind the caveat, that this works only for ICS+.

KG -
  • 7,130
  • 12
  • 56
  • 72
  • Not sure how I missed the answer by Nick Moukhine in that thread. That is exactly what I need when using Espresso, Retrofit and Dagger. – Austyn Mahoney May 05 '14 at 22:02
  • +1 this is a great suggestion, Ive been looking around a lot today about this stuff. Strangely my `Retrofit` + `Espresso` tests which involve async retrofit calls are just working out of the box, so i dont know if espresso now covers the way retrofit works by default ? – Dori Jul 25 '14 at 16:59
  • 4
    Should this be added only to tests through DI or is it ok to use in production? – Maragues Oct 03 '14 at 12:23
  • If doing that, make sure to disable all your circular `ProgressBar`. Use `indeterminateOnly=false` or `setVisibility(View.GONE)` otherwise it will hang then timeout. – mbmc Aug 08 '15 at 04:58
4

If you're using RxJava Observables with Retrofit 2.0 then you can use .subscribeOn(Schedulers.from(AsyncTask.THREAD_POOL_EXECUTOR)) instead of .subscribeOn(Schedulers.io()) and everything works fine!

OR alternatively you can override RxJavaSchedulersHook, allowing you to just make the change in one location. For example:

   public MySuperCoolClient() {

      if (BuildConfig.DEBUG) {
         configureIoSchedulerToUseAsyncTaskThreadPool();
      }

      this.restApi = new Retrofit.Builder()
              .baseUrl(Parameters.endpoint)
              .addConverterFactory(GsonConverterFactory.create(gsonBuilder()))
              .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
              .build()
              .create(RestApi.class);
   }

   private void configureIoSchedulerToUseAsyncTaskThreadPool() {
      RxJavaPlugins.getInstance().registerSchedulersHook(new RxJavaSchedulersHook() {
         @Override
         public Scheduler getIOScheduler() {
            return Schedulers.from(AsyncTask.THREAD_POOL_EXECUTOR);
         }
      });
   }
I_AM_PANDA
  • 532
  • 1
  • 4
  • 8
1

note answer below is based on Retrofit 1.6.1 - will update for newest version. Retrofit 1.9.0 does not allow you to set the HttpExecutor via the RestAdapter.Builder any longer

The accepted answer is a step in the right direction but it makes me feel uncomfortable. In practise you would either need to set the AsyncTask.THREAD_POOL_EXECUTOR for live & tests builds OR test builds only.

Setting for both would mean all your network IO pooling will depend on the aysnc queue implementation, which became serial by default for apps with target versions ICS+

Setting for tests only would mean that your test build is different from your live build, which imho is not a great place to start testing from. Also you may encounter test problems on older devices due to async pool changes.

It is rightly mentioned above that Espresso hooks into AsyncTask.THREAD_POOL_EXECUTOR already. Lets poke around...

How does it obtain this?

ThreadPoolExecutorExtractor

Who/what uses this?

BaseLayerModule has provideCompatAsyncTaskMonitor(ThreadPoolExecutorExtractor extractor) which returns an AsyncTaskPoolMonitor

How does that work? Have a look!

AsyncTaskPoolMonitor

Where is it used?

UiControllerImpl has method loopMainThreadUntilIdle() which manually calls asyncTaskMonitor.isIdleNow() before checking any user registered idlingResources with idlingResourceRegistry.allResourcesAreIdle()

Im guessing with Retrofit we can use the RestAdapter.Builder.setExecutors(...) method and pass in our own instance (or version) of the AsyncTaskPoolMonitor using the same http Executor that Retrofit is init on Android with

@Override Executor defaultHttpExecutor() {
      return Executors.newCachedThreadPool(new ThreadFactory() {
        @Override public Thread newThread(final Runnable r) {
          return new Thread(new Runnable() {
            @Override public void run() {
              Process.setThreadPriority(THREAD_PRIORITY_BACKGROUND);
              r.run();
            }
          }, RestAdapter.IDLE_THREAD_NAME);
        }
      });
    }

(from here)

And wrap this in the IdlingResource interface to use in our tests!!

The only question in that as Retrofit makes the callback using a separate Executor on the mainThread that relies on the main Looper, this may result in problems but Im assuming for the moment that Espresso is tied into this as well. Need to look into this one.

Dori
  • 18,283
  • 17
  • 74
  • 116
1

Retrofit 2 uses okhttp3, which, in turn uses a dispatcher. Jake Wharton created this library that monitors the dispatcher for idleness. You would create the IdlingResource like this:

IdlingResource resource = OkHttp3IdlingResource.create("OkHttp", okHttpClient);

Be aware that this might not suffice to be used for successful Espresso tests (I've tried) because the IdlingResource might say it's idle just before or after the http call, and your Espresso test would execute and fail instead of waiting.

My recommendation for these cases is to use a thread pool to launch any background tasks and make an IdlingResource wrapping this thread pool. See this article for more info: https://medium.com/@yair.kukielka/idlingresource-dagger-and-junit-rules-198e3ae791ff

Yair Kukielka
  • 10,686
  • 1
  • 38
  • 46
0

If you're using Asynctasks, you don't need to do anything because Espresso already knows how to wait for them: it uses AsyncTaskPoolMonitor which is a wrapper around the Asynctask thread pool.

If you're using you're own thread pool (that was my case), you could use this class that will wrap your executor so that Espresso can know when it's idle.

This great post explains how it works. I tried in my project and it's great! Using dagger, I get a hold of my thread pool and wrapped it in an IdlingResource in a junit @rule.

Yair Kukielka
  • 10,686
  • 1
  • 38
  • 46