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…
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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”
[…] Android: Testing an Intent Service […]
[WORDPRESS HASHCASH] The comment’s server IP (64.90.36.108) doesn’t match the comment’s URL host IP (64.90.36.155) and so is spam.