Sunday, October 28, 2012

Android Service test - callback method not firing

Preface
If you want to execute a long running operation in your program Service is a fine choice. You might ask why not using AsyncTask? The answer to it is simple. AsyncTask is for short operations (few seconds probably) and Service is for longer or more CPU intensive jobs. For better and easier integration, it’s better to test each unit of your program. Android JUnit provides set of classes for writing unit tests. ServiceTestCase is a class by which you can unit test your service class.

Scenario
Consider we have to send some data for a complex calculation to a remote server and wait for the response. We decided to use Service. In our example when user initiates the calculation our service start in the background and send the data to the server. Once the result is back it will update the application local database. Lets have a unit test for our Service.

Problem
Our service is an IntentService and it receives a serviceCallback (ResultReciever). The problem is, before the callback method (onReceiveResult) is called the teardown method of the our unit test terminates the test.

Solution
A solution to this problem could be somehow forcing the main thread to wait for the result of the callBack service method to return. For doing this we can make use of CountDownLatch. This class helps us to pause the thread until one or more operations finishes. Below is the ServiceTestCase utilizing the aforementioned mechanism.

await method of CountDownLatch cause the current thread to wait. When the response is back,  countDown method of CountDownLatch counts down and releases all the waiting threads.


 import java.util.HashMap;  
 import java.util.Map;  
 import java.util.Properties;  
 import java.util.concurrent.CountDownLatch;  
 import android.content.Intent;  
 import android.os.Bundle;  
 import android.os.ResultReceiver;  
 import android.test.ServiceTestCase;  
 public class CalculationServiceTest extends ServiceTestCase<CalculationService>{  
      public CalculationServiceTest() {  
           super(CalculationService.class);  
      }  
      public void testCalculation() throws InterruptedException{  
           //our synchronization aid   
           CountDownLatch signal = new CountDownLatch(1);  
           //passing object for evaluating the result of our service  
           Map<String, String> result = new HashMap<String, String>();  
           //Creating a new thread which is responsible for running our Service  
           new Thread(new WorkerRunnable( signal, result)).start();  
           //casuing the current to wait  
           signal.await();  
           assertEquals(CommunicationConstants.SERVICE_RESULT_SUCCESS, (String)result.get("result"));  
      }  
      class WorkerRunnable implements Runnable {  
           private  CountDownLatch doneSignal;  
           private Map<String, String> result;  
           public WorkerRunnable(CountDownLatch cdl, Map<String, String> result){  
                this.doneSignal = cdl;  
                this.result = result;  
           }  
           @Override  
           public void run() {  
                Intent startServiceIntent = new Intent();  
                startServiceIntent.putExtra(CalculationService.SERVICE_TYPE,  
                          Constant.CALCULATE);  
                ResultReceiver serviceCallback = new ResultReceiver(null) {  
                     @Override  
                     protected void onReceiveResult(int resultCode, Bundle resultData) {  
                          result.put("result", String.valueOf(resultCode));  
                          doneSignal.countDown();  
                     }  
                };  
                //setting a callback for our service  
                startServiceIntent.putExtra(CalculationService.SERVICE_CALLBACK,  
                          serviceCallback);  
                startServiceIntent.setClass(getContext(), CalculationService.class);  
                startService(startServiceIntent);  
           }  
      }  
 }  

No comments :