My little Android warehouse

Random thoughts about my experience as moonlight android developer.

Testing an Intent Service

It may sound weird, but I discovered java development while trying to learn android development itself.
Along with Java, eclipse and all this nice stuff, I discovered junit too.

One of the problems I encountered while developing my second app, droidalone, was to test intent services.

Intent services are a powerful way to handle background task. They basically are services that run in their own thread and die (like non sticky services) after they finished their job. They don't have all the problems related to running an async task inside an activity (like knowing the result of the task if the activity goes in background). DroidAlone makes an intensive use of intentservices, because every event is handled by an intent service.

A problem with testing the intent service itself (and in general, with a class that offers a "do in background, call a callback when finished" interface) is that the single test finishes when its caller function returns. I had to find a way to make the test function hang and wait while the background service (or thread) performs its job. Then the test function has to be waken up in order to evaluate if the job result is consistent with what is expected from the test.

The idea is pretty simple, a semaphore is locked until the task is done. When the task is done, the result is evaluated and then the semaphore is released.

Let's see an example.

Here the base class to perform this actions:


package com.wyb.testhelper;

import java.util.concurrent.Semaphore;

import android.content.BroadcastReceiver;
import android.content.Context;

/**
* Asynchronous calls tester class
* @author fede
*
*/
public class AsyncCallTestClass {
/**
* Interface to be implemented by the test implementation
* @author fede
*
*/
public interface AsyncCallTest{
/**
* The call to execute the test
*/
public void execTest();
}

private Semaphore mSem;
private AsyncCallTest mTest;

public AsyncCallTestClass(Context c,
AsyncCallTest test) {
mSem = new Semaphore(0);
mTest = test;


}

/**
* must be called when the test is finished and the result was evaluated
*/
public void testFinished(){
mSem.release();
}


/**
* Runs the test itself
*/
public void runTest(){
mTest.execTest();
try{
mSem.acquire();
}catch(InterruptedException e){

}
}



}


It accepts an interface that exposes execTest method, and offers a "testFinished" method to be called when the test result is evaluated.

My intent service helper class accept a message to be processed in background, and offers a ResultNotify interface whose methods will be called when the task is finished.
Several kinds of messages can be processed, and I had to test all of them.

/**
* Helper class to test several kinds of messages
* @author fede
*
*/
public class ServiceHelperTestClass {
/**
* Callbacks will be called when the test is finished
* @author fede
*
*/
public interface ServiceTestFinishedInterface{
/**
* Asynchronous task finished (result needs to be evaluated)
*/
public void onCallFinished();

/**
* Asyncronous task returned an error
* @param message
*/
public void onError(String message);
}

Context mContext;
ServiceTestFinishedInterface mFinish;


/**
* Constructor
* @param m message to be tested
* @param context context to run the test against
* @param finish interface that will be called when the task will finish
*/
public ServiceHelperTestClass(MessageToIntentService m,
Context context,
ServiceTestFinishedInterface finish) {
mContext = context;
mFinish = finish;
final MessageToIntentService mess = m;
final IntentServiceHelper helper = IntentServiceHelper.getInstance();
final AsyncCallTestClass c = new AsyncCallTestClass(mContext,
new AsyncCallTest(){
@Override
public void execTest() {
try{
helper.processMessage(mContext, mess);
}catch (Exception e){
mFinish.onError(e.getMessage());
}
}

});

// A result interface needs to be registered
helper.registerResultListener(new IntentServiceResultNotify(){

@Override
public void onIntentServiceError(String result) {
mFinish.onError(result);
c.testFinished();
}

@Override
public void onIntentServiceResult(String result) {
mFinish.onCallFinished();
c.testFinished();
}

}, mContext);



c.runTest();

}

}




In this way, to test all the types of messages, OR to test the intent service under different scenarios, all I have to do in my test class is to implement a method like:


public void testHelloMessage() {
ServiceHelperTestClass t =

new ServiceHelperTestClass(new HelloMessasge(),
mContext,
new ServiceTestFinishedInterface(){

@Override
public void onCallFinished() {
// Test Result here
;
}

@Override
public void onError(String message) {
fail(message);
}

});
}


This was just an example. However the basic idea is to make the test call block, and to unblock it (with a callback or with a broadcast receiver) when the task is finished.

Comments

bgenchel
its hard to follow your code. why declare an interface. is that of type interface or did you define it yourself? where is AsyncTest defined? I'm trying this myself and its not working.