Discovering Espresso for Android: implementing CountingIdlingResource
This post shows how to use Espresso IdlingResource that keeps a counter to determine application under test idleness. Such IdlingResource is called CountingIdlingResource and is used to controll test access to the application UI. When the counter is 0 – it is considered to be idle, when it is non-zero it is not idle. This is very similar to the way a java.util.concurrent.Semaphore
behaves.
The counter may be incremented or decremented from any thread. If it reaches an illogical state (like counter less than zero) it will throw an IllegalStateException
. This class can then be used to wrap up operations that while in progress should block tests from accessing the UI.
At second, why do I need it? Espresso developers claim that using their test framework you can “Leave your waits, syncs, sleeps, and polls behind and let Espresso gracefully manipulate and assert on the application UI when it is at rest.” This is true, BUT if you are using AsyncTask
while performing the network calls.
If your application under test doesn’t use for some reason AsyncTask
and uses ThreadPoolExecutor
for network calls. Then this is your case.
It is really pretty easy to register CountingIdlingResource for this thread pool in your test package having getter and setter for the ThreadPoolExecutor
instance in main app. The only requirement is to make this instance static to be able to set it from your test package. Below is an example how getter and setter look like in mine code:
public static ThreadPoolExecutor executor; public void setExecutor(ThreadPoolExecutor executorNew){ executor = executorNew; } public ThreadPoolExecutor getExecutor(){ if (executor == null){ executor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAX_POOL_SIZE, 1, TimeUnit.MILLISECONDS, workingQueue); } return executor; }
Then in your test package you can do the following :
- Create your custom
CountingIdlingResource
class which extendsThreadPoolExecutor
and implementsIdlingResource
. - Override
execute(…)
method fromThreadPoolExecutor
and increase the thread counter every time you hit it. - Override
afterExecute(…)
method to be able to decrease the thread counter when call finished and notify the Espresso when the thread counter is equal to zero that you areonTransitionToIdle
state. - What I also find useful is to have method in this class that registers
CountingIdlingResource
for Espresso –registerCounterIdlingResources()
.
class MyCountingIdlingResource extends ThreadPoolExecutor implements IdlingResource { IdlingResource.ResourceCallback callback; private int threadCount = 0; static MyCountingIdlingResource idlingResource; static PriorityBlockingQueue workingQueue; static boolean flag = false; public MyCountingIdlingResource( int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) { super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue); } @Override public synchronized void execute(Runnable r) { super.execute(r); threadCount++; } @Override protected synchronized void afterExecute(Runnable r, Throwable t) { super.afterExecute(r, t); threadCount--; if (threadCount == 0) { if (null != callback) { callback.onTransitionToIdle(); } } } @Override public String getName() { return "MyCountingIdlingResource"; } @Override public boolean isIdleNow() { return threadCount == 0; } @Override public void registerIdleTransitionCallback(ResourceCallback callback) { this.callback = callback; } public static void registerCounterIdlingResources (){ if (!flag){ workingQueue = new PriorityBlockingQueue<>(); MyCountingIdlingResource exec = new MyCountingIdlingResource(2, 3, 1, TimeUnit.MILLISECONDS, workingQueue); MyCustomPoolRequestHelper poolRequestHelper = new MyCustomPoolRequestHelper(); poolRequestHelper.setExecutor(exec); registerIdlingResources(exec); flag = true; } } }
After you made all these steps then registering of CountingIdlingResource
looks really easy. Register it in your setUp()
method like that:
MyCountingIdlingResource.registerCounterIdlingResources();
And here is small but useful tip – I use the static boolean flag variable to check if I’ve already register the same CountingIdlingResource for my test suite. If yes then I simply skip it the next time.
As you may know, Espresso can ignore registerIdlingResources()
with the same names but in mine setup I don’t want to create once again instances of MyCustomPoolRequestHelper
which is different from that one which was registered at first step and can cause test failure.