Categories
mobile Programming

Android: Testing an Intent Service

There’s a handy thing in Espresso called the ServiceTestRule, which is for testing Services. Yay. I thought it was just what I needed until I read this bit of the documentation

Note: The ServiceTestRule class does not support testing of IntentService objects. If you need to test a IntentService object, you should encapsulate the logic in a separate class and create a corresponding unit test instead.

OK, first up – refactoring my IntentService. This class already did very little, poking something else depending on the kind of Intent it received, and then triggering another intent when the work was done. So it was pretty straightforward to refactor everything out into a Helper class. This class takes the intent, does the work, and returns an intent, which is then thrown.


package co.ortatech.showandhide.service;
import android.app.IntentService;
import android.content.Intent;
import android.support.v4.content.LocalBroadcastManager;
import javax.inject.Inject;
import co.ortatech.showandhide.application.ShowAndHideApplication;
public class ImageProcessingService extends IntentService {
@Inject ImageProcessingServiceHelper helper;
public ImageProcessingService() {
super(ImageProcessingService.class.getName());
}
public ImageProcessingService(String name) {
super(name);
}
@Override
public void onCreate() {
super.onCreate();
((ShowAndHideApplication) getApplication()).component().inject(this);
}
@Override
protected void onHandleIntent(Intent workIntent) {
Intent localIntent = helper.handleAndReturnAppropriateIntent(workIntent,
this.getContentResolver());
// If the action fails, the intent can be null.
if (localIntent != null) {
LocalBroadcastManager.getInstance(this).sendBroadcast(localIntent);
}
}
}

This makes testing more straightforward because I just need to mock the helper, and return the requisite intent for whatever was supposed to happen.

Testing the helper was also easier, which made it easier to be more thorough. The only thing I ran into was needing an ActivityRule (in order to get the ContentResolver).

But – what about testing the thing that calls the IntentService? That was harder. Firstly, I set up IdlingResources, as explained by Chiu-Ki. But there’s an intent launched and received as the activity starts, which  complicates some things. I ended up verifying and resetting all my mocks for each test twice – once after setup completes, and the second time after the end of the test.

First I got all tests running individually, but when I started combining them I was running into issues that I was pretty confident were concurrency issues. I asked Chiu-Ki to check, and on a faster emulator (and probably a better computer) things were fine… but they weren’t for me. I wanted to add UiController.loopMainThreadForAtLeast as explained in this blogpost, but that looked a bit complicated.

To check if I was right, I added Thread.sleep(100); after each mock of the Helper. And yay! Everything worked! My enquiries into UiController.loopMainThreadForAtLeast hadn’t left me optimistic about it, so I left it at that.

Ideally you don’t want to be using sleep() in tests. But I subscribe to the idea that any test is better than no test at all. Maybe I’ll be able to rewrite things so that this isn’t necessary, but I’m not that hopeful – part of the reason why this is necessary is because it’s a complicated process, and I’m trying to improve the perceived performance by breaking things up, which is why the Activity is so entwined with the IntentService.

Apparently one option is Fork, which looks super useful. However if you use it, you need to run your tests from the command line. I really want to be able to run my tests from Android Studio (especially as I haven’t set up CI on this project), so I guess I will live with sleep()

One reply on “Android: Testing an Intent Service”

Comments are closed.