In Android, test is not as easy as any other platform. Because Android test cannot be run without emulator. Particulary when it comes to AsyncTask or Service, it is difficult to test because they are different type of thread and hard to check their result. Then, how can we ensure the result of AsyncTask valid?
AsyncTask is a thread and an asynchnorous as the name means. So, we need to wait for it finishes its job and need to capture the event. Then, when it happens in AsyncTask. It can be one of onBackground() and onPostExecute() methods. It doesn't matter you use onBackground() or onPostExecute() but I prefer onPostExecute(). Anyway, we can test an AsyncTask if we can hook it. Then, how can we hook it? For that, we can use callback pattern. But we need to delay main thread to wait for the AsyncTask's job done because we want to check the result.
So the structure for the test would be like:
1. Create AsyncTask A 2. Injection a callback into A 3. Wait until A finish 4. Invoke callback in A.postExecute() 5. Verify the resultWe can use
java.util.concurrent.CountDownLatch
for synchronising more than one thread. CountDownLatch is a synchronisation aid that allows one or more threads to wait until a set of operations being performed in other threads completes. Here are some methods in CountDownLatch class we are going to use for the test:
await() | Causes the current thread to wait until the latch has counted down to zero, unless the thread is interrupted. | countDown() | Decrements the count of the latch, releasing all waiting threads if the count reaches zero. |
We are going to use http://www.bbc.co.uk/radio1/playlist.json to get some JSON data. Network operation is very common and useful case for AsyncTask. I'd like to get the JSON and parse and display it in a list view.
First create AyncTask class to get the JSON.import android.os.AsyncTask; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; /** * Created by Mark Sunghun Park */ public class JsonGetTask extends AsyncTaskI think that it would be loose coupling if you do not put too many code in an AsyncTask. You may show progress dialog before executing an AsyncTask and dismiss it after job done. For this, you may pass context of an activity to the AsyncTask instance. But it makes for you to test it because your test rely on another Activity or context inherited object. I just set an interface to get callback from the AsyncTask. You can make it less-coupling using a listener.{ private static final String TAG = "JsonGetTask"; private JsonGetTaskListener mListener = null; private Exception mError = null; @Override protected String doInBackground(String... params) { String content = null; try { content = getJson(params[0]); } catch (RuntimeException e){ mError = e; } return content; } public JsonGetTask setListener(JsonGetTaskListener listener) { this.mListener = listener; return this; } @Override protected void onPostExecute(String s) { if (this.mListener != null) this.mListener.onComplete(s, mError); } @Override protected void onCancelled() { if (this.mListener != null) { mError = new InterruptedException("AsyncTask cancelled"); this.mListener.onComplete(null, mError); } } private String getJson(String address){ try { URL url = new URL(address); URLConnection conn = url.openConnection(); StringBuffer sb = new StringBuffer(); BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream())); String line = null; while ((line = br.readLine())!= null){ sb.append(line); } br.close(); return sb.toString(); } catch (MalformedURLException e) { e.printStackTrace(); throw new IllegalArgumentException("Invalid URL"); } catch (IOException e) { e.printStackTrace(); throw new RuntimeException("Network error"); } } public static interface JsonGetTaskListener { public void onComplete(String jsonString, Exception e); } }
Here is the test code:
import android.app.Application; import android.test.ApplicationTestCase; import android.text.TextUtils; import java.util.concurrent.CountDownLatch; public class ApplicationTest extends ApplicationTestCase{ String mJsonString = null; Exception mError = null; CountDownLatch signal = null; public ApplicationTest() { super(Application.class); } @Override protected void setUp() throws Exception { signal = new CountDownLatch(1); } @Override protected void tearDown() throws Exception { signal.countDown(); } public void testAlbumGetTask() throws InterruptedException { JsonGetTask task = new JsonGetTask(); task.setListener(new JsonGetTask.JsonGetTaskListener() { @Override public void onComplete(String jsonString, Exception e) { mJsonString = jsonString; mError = e; signal.countDown(); } }).execute("http://www.bbc.co.uk/radio1/playlist.json"); signal.await(); assertNull(mError); assertFalse(TextUtils.isEmpty(mJsonString)); assertTrue(mJsonString.startsWith("{\"playlist\"")); assertTrue(mJsonString.endsWith("}")); } }
new CountDownLatch(1)
is passed int number. It is the number of times countDown() must be invoked before threads can pass through await(). If it is negative, it gives IllegalArgumentException.
signal.await()
waits before signal.countDown()
is called. If you want set timeout, you can callawait(long timeout, TimeUnit unit)
(eg. singal.await(10, TimeUnit.SECOND)) instead.
CountDownLatch cause java.lang.IllegalMonitorStateException
ReplyDeleteI don't recommend to use this way to test AsyncTask. It's much better to use Robolectric and I'm also using that.
Deletecan you make something like this on how to use roboelectric?
Deletereally help me..thank for the author
ReplyDeleteI think the test code is old school now. I'm looking at some chance to using RxJava/RxAndroid to support more fluent way to test. Also the getJson method in the AsyncTask should be mocked to avoid any dependency on server. Unit test should be executed without any coupling.
ReplyDeleteNice it seems to be good post... It will get readers engagement on the article since readers engagement plays an vital role in every blog.. i am expecting more updated posts from your hands.
ReplyDeleteMobile App Development Company
Mobile App Development Company in India
Android app Development Company
ios app development Company
Mobile App Development Companies
I've been doing many different roles, web, mobile, java and javascript backend. I haven't committed to Android very much in the mean time. I will try to do more.
DeletePretty article! I found some useful information in your blog, it was awesome to read, thanks for sharing this great content to my vision, keep sharing..
ReplyDeleteMobile App Development Company
Android App Development Company