100

I have a very simple AsyncTask implementation example and am having problem in testing it using Android JUnit framework.

It works just fine when I instantiate and execute it in normal application. However when it's executed from any of Android Testing framework classes (i.e. AndroidTestCase, ActivityUnitTestCase, ActivityInstrumentationTestCase2 etc) it behaves strangely:

  • It executes doInBackground() method correctly
  • However it doesn't invokes any of its notification methods (onPostExecute(), onProgressUpdate(), etc) -- just silently ignores them without showing any errors.

This is very simple AsyncTask example:

package kroz.andcookbook.threads.asynctask;

import android.os.AsyncTask;
import android.util.Log;
import android.widget.ProgressBar;
import android.widget.Toast;

public class AsyncTaskDemo extends AsyncTask<Integer, Integer, String> {

AsyncTaskDemoActivity _parentActivity;
int _counter;
int _maxCount;

public AsyncTaskDemo(AsyncTaskDemoActivity asyncTaskDemoActivity) {
    _parentActivity = asyncTaskDemoActivity;
}

@Override
protected void onPreExecute() {
    super.onPreExecute();
    _parentActivity._progressBar.setVisibility(ProgressBar.VISIBLE);
    _parentActivity._progressBar.invalidate();
}

@Override
protected String doInBackground(Integer... params) {
    _maxCount = params[0];
    for (_counter = 0; _counter <= _maxCount; _counter++) {
        try {
            Thread.sleep(1000);
            publishProgress(_counter);
        } catch (InterruptedException e) {
            // Ignore           
        }
    }
}

@Override
protected void onProgressUpdate(Integer... values) {
    super.onProgressUpdate(values);
    int progress = values[0];
    String progressStr = "Counting " + progress + " out of " + _maxCount;
    _parentActivity._textView.setText(progressStr);
    _parentActivity._textView.invalidate();
}

@Override
protected void onPostExecute(String result) {
    super.onPostExecute(result);
    _parentActivity._progressBar.setVisibility(ProgressBar.INVISIBLE);
    _parentActivity._progressBar.invalidate();
}

@Override
protected void onCancelled() {
    super.onCancelled();
    _parentActivity._textView.setText("Request to cancel AsyncTask");
}

}

This is a test case. Here AsyncTaskDemoActivity is a very simple Activity providing UI for testing AsyncTask in mode:

package kroz.andcookbook.test.threads.asynctask;
import java.util.concurrent.ExecutionException;
import kroz.andcookbook.R;
import kroz.andcookbook.threads.asynctask.AsyncTaskDemo;
import kroz.andcookbook.threads.asynctask.AsyncTaskDemoActivity;
import android.content.Intent;
import android.test.ActivityUnitTestCase;
import android.widget.Button;

public class AsyncTaskDemoTest2 extends ActivityUnitTestCase<AsyncTaskDemoActivity> {
AsyncTaskDemo _atask;
private Intent _startIntent;

public AsyncTaskDemoTest2() {
    super(AsyncTaskDemoActivity.class);
}

protected void setUp() throws Exception {
    super.setUp();
    _startIntent = new Intent(Intent.ACTION_MAIN);
}

protected void tearDown() throws Exception {
    super.tearDown();
}

public final void testExecute() {
    startActivity(_startIntent, null, null);
    Button btnStart = (Button) getActivity().findViewById(R.id.Button01);
    btnStart.performClick();
    assertNotNull(getActivity());
}

}

All this code is working just fine, except the fact that AsyncTask doesn't invoke it's notification methods when executed by within Android Testing Framework. Any ideas?

Rahul Sharma
  • 2,867
  • 2
  • 27
  • 40
Vladimir Kroz
  • 5,237
  • 6
  • 39
  • 50

9 Answers9

127

I met a similar problem while implementing some unit-test. I had to test some service which worked with Executors, and I needed to have my service callbacks sync-ed with the test methods from my ApplicationTestCase classes. Usually the test method itself finished before the callback would be accessed, so the data sent via the callbacks would not be tested. Tried applying the @UiThreadTest bust still didn't work.

I found the following method, which worked, and I still use it. I simply use CountDownLatch signal objects to implement the wait-notify (you can use synchronized(lock){... lock.notify();}, however this results in ugly code) mechanism.

public void testSomething(){
final CountDownLatch signal = new CountDownLatch(1);
Service.doSomething(new Callback() {

  @Override
  public void onResponse(){
    // test response data
    // assertEquals(..
    // assertTrue(..
    // etc
    signal.countDown();// notify the count down latch
  }

});
signal.await();// wait for callback
}
Peter Ajtai
  • 56,972
  • 13
  • 121
  • 140
bandi
  • 1,286
  • 1
  • 9
  • 2
  • 1
    What is `Service.doSomething()`? – Peter Ajtai Nov 24 '11 at 06:28
  • 12
    I'm testing an AsynchTask. Did this, and, well, the background task is seems never be called and singnal waits forever :( – User May 07 '12 at 13:29
  • @Ixx, did you call `task.execute(Param...)` before `await()` and put `countDown()` in `onPostExecute(Result)`? (see http://stackoverflow.com/a/5722193/253468) Also @PeterAjtai, `Service.doSomething` is an async call like `task.execute`. – TWiStErRob Nov 12 '13 at 00:22
  • what a lovely and simple solution. – Maciej Beimcik May 27 '18 at 06:52
  • `Service.doSomething()` is where you should replace your service/async task call. Make sure to call `signal.countDown()` on any method you need to implement or your test will get stuck. – Victor Oliveira Jul 13 '18 at 21:52
95

I found a lot of close answers but none of them put all the parts together correctly. So this is one correct implementation when using an android.os.AsyncTask in your JUnit tests cases.

 /**
 * This demonstrates how to test AsyncTasks in android JUnit. Below I used 
 * an in line implementation of a asyncTask, but in real life you would want
 * to replace that with some task in your application.
 * @throws Throwable 
 */
public void testSomeAsynTask () throws Throwable {
    // create  a signal to let us know when our task is done.
    final CountDownLatch signal = new CountDownLatch(1);

    /* Just create an in line implementation of an asynctask. Note this 
     * would normally not be done, and is just here for completeness.
     * You would just use the task you want to unit test in your project. 
     */
    final AsyncTask<String, Void, String> myTask = new AsyncTask<String, Void, String>() {

        @Override
        protected String doInBackground(String... arg0) {
            //Do something meaningful.
            return "something happened!";
        }

        @Override
        protected void onPostExecute(String result) {
            super.onPostExecute(result);

            /* This is the key, normally you would use some type of listener
             * to notify your activity that the async call was finished.
             * 
             * In your test method you would subscribe to that and signal
             * from there instead.
             */
            signal.countDown();
        }
    };

    // Execute the async task on the UI thread! THIS IS KEY!
    runTestOnUiThread(new Runnable() {

        @Override
        public void run() {
            myTask.execute("Do something");                
        }
    });       

    /* The testing thread will wait here until the UI thread releases it
     * above with the countDown() or 30 seconds passes and it times out.
     */        
    signal.await(30, TimeUnit.SECONDS);

    // The task is done, and now you can assert some things!
    assertTrue("Happiness", true);
}
Billy Brackeen
  • 951
  • 6
  • 3
  • 1
    thank for writing up a complete example... I was having a lot of small problems implementing this. – Peter Ajtai Nov 24 '11 at 06:40
  • Just over 1 year later, and you saved me. Thank you Billy Brackeen! – MaTT Jul 26 '12 at 20:02
  • This was working perfectly for me and then out of the blue all of a sudden test failures even if I run the code included in the sample above with the final assert validating the countdown is 0, very strange. – Syntax Apr 04 '13 at 05:02
  • 8
    If you want a timeout to count as a test failure, you can do: `assertTrue(signal.await(...));` – Jarett Millard Aug 27 '13 at 21:40
  • More then 3 years later and you saved one more tester enthusiast.. =).. Thanks – Renan Franca Sep 15 '14 at 20:05
  • 4
    Hey Billy I have tried this implementation but runTestOnUiThread is not found. Should the test case extend AndroidTestCase or does it need to extend ActivityInstrumentationTestCase2? – Doug Ray Dec 01 '15 at 19:00
  • 3
    @DougRay I had the same issue- if you extend InstrumentationTestCase then runTestOnUiThread will be found. – Luminaire Apr 02 '16 at 23:58
  • @JarettMillard I did it like `assertEquals(0, signal.getCount());`, but your way is more clear. – alfoks Dec 29 '16 at 13:32
25

The way to deal with this is to run any code that invokes an AsyncTask in runTestOnUiThread():

public final void testExecute() {
    startActivity(_startIntent, null, null);
    runTestOnUiThread(new Runnable() {
        public void run() {
            Button btnStart = (Button) getActivity().findViewById(R.id.Button01);
            btnStart.performClick();
        }
    });
    assertNotNull(getActivity());
    // To wait for the AsyncTask to complete, you can safely call get() from the test thread
    getActivity()._myAsyncTask.get();
    assertTrue(asyncTaskRanCorrectly());
}

By default junit runs tests in a separate thread than the main application UI. AsyncTask's documentation says that the task instance and the call to execute() must be on the main UI thread; this is because AsyncTask depends on the main thread's Looper and MessageQueue for its internal handler to work properly.

NOTE:

I previously recommended using @UiThreadTest as a decorator on the test method to force the test to run on the main thread, but this isn't quite right for testing an AsyncTask because while your test method is running on the main thread no messages are processed on the main MessageQueue — including the messages the AsyncTask sends about its progress, causing your test to hang.

Alex Pretzlav
  • 15,505
  • 9
  • 57
  • 55
  • That saved me... although I had to call "runTestOnUiThread" from yet another thread otherwise, I would get "This method can not be called from the main application thread" – Matthieu Feb 04 '12 at 00:25
  • @Matthieu Were you using `runTestOnUiThread()` from a test method with the `@UiThreadTest` decorator? That won't work. If a test method does not have `@UiThreadTest`, it should run on its own non-main thread by default. – Alex Pretzlav Feb 05 '12 at 18:42
  • Oh, that could me. I'll double check that. – Matthieu Feb 05 '12 at 21:34
  • Nope, did not work. The postExecute method of the ASyncTask is never getting called... Anyway, got something working (even if it's not very pretty) thanks to you. – Matthieu Feb 07 '12 at 23:25
  • 1
    That answer is pure jewel. It should be reworked to emphasize on update, if you really want to keep initial answer, put it as some background explanation and a common pitfall. – Snicolas Jun 13 '13 at 08:49
  • @Snicolas, good point. I've rewritten my answer to emphasize the "update" and gotten rid of the old incorrect code. – Alex Pretzlav Jul 02 '13 at 16:39
  • asyncTask.get() looks like a better approach to me. No more CountDownLatch to make code messy. I also tried to create a new asyncTask object in the test and then called execute() in runTestOnUiThread directly. And it seems it still works fine! Thanks for this wonderful answer! – tanghao Aug 28 '16 at 02:16
  • 1
    Documentation states method `Deprecated in API level 24` https://developer.android.com/reference/android/test/InstrumentationTestCase.html#runTestOnUiThread(java.lang.Runnable) – Ivar Aug 02 '19 at 12:01
  • 1
    Deprecated, use `InstrumentationRegistry.getInstrumentation().runOnMainSync()` instead! – Marco7757 Aug 09 '19 at 15:03
5

If you don't mind executing the AsyncTask in the caller thread (should be fine in case of Unit testing), you can use an Executor in the current thread as described in https://stackoverflow.com/a/6583868/1266123

public class CurrentThreadExecutor implements Executor {
    public void execute(Runnable r) {
        r.run();
    }
}

And then you run your AsyncTask in your unit test like this

myAsyncTask.executeOnExecutor(new CurrentThreadExecutor(), testParam);

This is only working for HoneyComb and higher.

robUx4
  • 853
  • 9
  • 13
5

I wrote enough unitests for Android and just want to share how to do that.

First off, here is helper class that responsible to wait and release waiter. Nothing special:

SyncronizeTalker

public class SyncronizeTalker {
    public void doWait(long l){
        synchronized(this){
            try {
                this.wait(l);
            } catch(InterruptedException e) {
            }
        }
    }



    public void doNotify() {
        synchronized(this) {
            this.notify();
        }
    }


    public void doWait() {
        synchronized(this){
            try {
                this.wait();
            } catch(InterruptedException e) {
            }
        }
    }
}

Next, lets create interface with one method that should be called from AsyncTask when work is done. Sure we also want to test our results:

TestTaskItf

public interface TestTaskItf {
    public void onDone(ArrayList<Integer> list); // dummy data
}

Next lets create some skeleton of our Task that we gonna test:

public class SomeTask extends AsyncTask<Void, Void, SomeItem> {

   private ArrayList<Integer> data = new ArrayList<Integer>(); 
   private WmTestTaskItf mInter = null;// for tests only

   public WmBuildGroupsTask(Context context, WmTestTaskItf inter) {
        super();
        this.mContext = context;
        this.mInter = inter;        
    }

        @Override
    protected SomeItem doInBackground(Void... params) { /* .... job ... */}

        @Override
    protected void onPostExecute(SomeItem item) {
           // ....

       if(this.mInter != null){ // aka test mode
        this.mInter.onDone(data); // tell to unitest that we finished
        }
    }
}

At last - our unitest class:

TestBuildGroupTask

public class TestBuildGroupTask extends AndroidTestCase  implements WmTestTaskItf{


    private SyncronizeTalker async = null;

    public void setUP() throws Exception{
        super.setUp();
    }

    public void tearDown() throws Exception{
        super.tearDown();
    }

    public void test____Run(){

         mContext = getContext();
         assertNotNull(mContext);

        async = new SyncronizeTalker();

        WmTestTaskItf me = this;
        SomeTask task = new SomeTask(mContext, me);
        task.execute();

        async.doWait(); // <--- wait till "async.doNotify()" is called
    }

    @Override
    public void onDone(ArrayList<Integer> list) {
        assertNotNull(list);        

        // run other validations here

       async.doNotify(); // release "async.doWait()" (on this step the unitest is finished)
    }
}

That's all.

Hope it will help to someone.

Maxim Shoustin
  • 77,483
  • 27
  • 203
  • 225
4

This can be used if you want to test the result from the doInBackground method. Override the onPostExecute method and perform the tests there. To wait for the AsyncTask to complete use CountDownLatch. The latch.await() waits till the countdown runs from 1 (which is set during initialization) to 0 (which is done by the countdown() method).

@RunWith(AndroidJUnit4.class)
public class EndpointsAsyncTaskTest {

    Context context;

    @Test
    public void testVerifyJoke() throws InterruptedException {
        assertTrue(true);
        final CountDownLatch latch = new CountDownLatch(1);
        context = InstrumentationRegistry.getContext();
        EndpointsAsyncTask testTask = new EndpointsAsyncTask() {
            @Override
            protected void onPostExecute(String result) {
                assertNotNull(result);
                if (result != null){
                    assertTrue(result.length() > 0);
                    latch.countDown();
                }
            }
        };
        testTask.execute(context);
        latch.await();
    }
Dan Lowe
  • 51,713
  • 20
  • 123
  • 112
0

How about using join?

fun myTest() = runBlocking {
    CoroutineScope(Dispatchers.IO).launch {
        // test something here
    }.join()
}
Ilya Gazman
  • 31,250
  • 24
  • 137
  • 216
0

Use this simple solution

runBlocking{
   //Your code here
}
Anga
  • 2,450
  • 2
  • 24
  • 30
-1

Most of those solutions require a lot of code to be written for every test or to change your class structure. Which I find very difficult to use if you have many situations under test or many AsyncTasks on your project.

There is a library which eases the process of testing AsyncTask. Example:

@Test
  public void makeGETRequest(){
        ...
        myAsyncTaskInstance.execute(...);
        AsyncTaskTest.build(myAsyncTaskInstance).
                    run(new AsyncTest() {
                        @Override
                        public void test(Object result) {
                            Assert.assertEquals(200, (Integer)result);
                        }
                    });         
  }       
}

Basically, it runs your AsyncTask and test the result it returns after the postComplete() has been called.

Rahul Sharma
  • 2,867
  • 2
  • 27
  • 40