I am writing a unit test for a service using ServiceTestCase.
The service basically executes an AsyncTask that does some work and then does something else in onPostExecute().
The service works as expected when I run and debug it in a (virtual) device.
But in the test extending ServiceTestCase, I only get into doInBackground(). Once the methods returns, onPostExecute() never gets called. I am letting the test sleep() so the AsyncTask has time to complete its work.
This is the simplified service:
public class ServiceToTest extends Service {
private AtomicBoolean busy = new AtomicBoolean(false);
@Override
public IBinder onBind(final Intent intent) {
return null;
}
@Override
public int onStartCommand(final Intent intent, final int flags,
final int startId) {
this.handleCommand();
return START_NOT_STICKY;
}
/**
* Workaround for http://code.google.com/p/android/issues/detail?id=12117
*/
@Override
public void onStart(final Intent intent, final int startId) {
this.handleCommand();
}
public void handleCommand() {
new TaskToTest().execute();
}
public boolean isBusy() {
return busy.get();
}
private class TaskToTest extends AsyncTask<Boolean, Void, TestInfo> {
@Override
protected void onPreExecute() {
开发者_C百科 busy.set(true);
}
@Override
protected TestInfo doInBackground(final Boolean... args) {
return null;
}
@Override
protected void onPostExecute(final TestInfo info) {
busy.set(false);
}
}
}
An this is the test for it:
public class ServiceTest extends ServiceTestCase<ServiceToTest> {
public ServiceTest() {
super(ServiceToTest.class);
}
public void testIsBusy() throws InterruptedException {
startService(new Intent("this.is.the.ServiceToTest"));
ServiceToTest serviceToTest = this.getService();
assertTrue(serviceToTest.isBusy());
Thread.sleep(10000);
assertFalse(serviceToTest.isBusy());
}
}
I suppose that the environment provided by ServiceTestCase is somewhat limited so this doesn't work, but is there anything I can do to make it work anyway?
Cheers, Torsten
The problem is that your background thread is waiting for the UI to be "alive", you need to call Looper.prepare()
and Looper.loop()
. It's better explained in this page.
So just to follow up on how I got it to work with the info provided by dmon.
I changed my test to the below:
public class ServiceTest extends ServiceTestCase {
public ServiceTest() {
super(ServiceToTest.class);
}
public void testIsBusy() throws InterruptedException {
// Starts the service and asserts that onPreExecute() was called
ServiceTestThread serviceTestThread = new ServiceTestThread();
serviceTestThread.start();
// Wait for the service to start and complete doInBackground()
// TODO Implement something smarter than this...
Thread.sleep(1000);
// Assert that onPostExecute() was called
assertFalse(serviceTestThread.serviceToTest.isBusy());
}
private class ServiceTestThread extends Thread {
ServiceToTest serviceToTest;
public void run() {
Looper.prepare();
startService(new Intent("this.is.the.ServiceToTest"));
serviceToTest = getService();
assertTrue(serviceToTest.isBusy());
Looper.loop();
}
}
}
I'll see now to make this ServiceTestThread more generic so it can be reused.
Torsten
Not sure if this is useful to anybody else, but this was my attempt at abstracting Tortens answer and making it more reusable.
private synchronized boolean getWaitFlag()
{
return _waitFlag;
}
private boolean _waitFlag;
private synchronized void setWaitFlag(boolean value)
{
_waitFlag = value;
}
private void waitForCompletionFlag() throws InterruptedException
{
Calendar cal = Calendar.getInstance();
while (getWaitFlag() == false)
{
Thread.sleep(10);
if (Calendar.getInstance().getTimeInMillis() - cal.getTimeInMillis() > 1000) // Wait at most 1 second
{
Log.e("timeout", "timed out waiting to complete task");
break;
}
}
}
private abstract class EmulatedUI extends Thread
{
public abstract void doWork();
public void run()
{
Looper.prepare();
doWork();
Looper.loop();
}
}
public void testSomething() throws InterruptedException
{
EmulatedUI thread = new EmulatedUI() {
@Override
public void doWork()
{
_objectToTest.someAsyncCall(new WorkCompletedCallback() {
@Override
public void onComplete()
{
// could possibly assert things here
setWaitFlag(true);
}
});
}
};
thread.start();
waitForCompletionFlag();
// assert things here since you know the async task has completed.
}
I had the same problem when attempting to bind to a service from the test runner thread, rather than the ui thread. Try calling startService from the ui thread.
public void testIsBusy() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
startService(new Intent("this.is.the.ServiceToTest"));
ServiceToTest serviceToTest = this.getService();
assertTrue(serviceToTest.isBusy());
Thread.sleep(10000);
assertFalse(serviceToTest.isBusy());
latch.countDown();
}
});
latch.await(5, TimeUnit.SECONDS);
}
精彩评论