I'm trying to test the onHandleIntent()
method of an IntentService
using Robolectric
.
I'm starting the service with:
Activity activity = new Activity();
Intent intent = new Intent(activity, MyService.class);
activity.startService(intent);
ShadowActivity shadowActivity = Robolectric.shadowOf(activity);
Intent startedIntent = shadowActivity.getNextStartedService();
assertNotNull(startedIntent);
seems like startedIntent
is not null, but onHandleIntent()
doesn't seem to be called.
how should I test it ?
Robolectric has a ServiceController
that can go thru service lifecycle just like activity. This controller provides all methods to execute corresponding service callbacks (e.g. controller.attach().create().startCommand(0, 0).destroy()
).
Theoretically we can expect that IntentService.onStartCommand()
will trigger IntentService.onHandleIntent(Intent)
, via its internal Handler
. However this Handler
uses a Looper
which runs on a background thread, and I have no idea how to make this thread advance to next task. A workaround would be to create TestService
that mimics the same behavior, but triggers onHandleIntent(Intent)
on main thread (thread used to run tests).
@RunWith(RobolectricGradleTestRunner.class)
public class MyIntentServiceTest {
private TestService service;
private ServiceController<TestService> controller;
@Before
public void setUp() {
controller = Robolectric.buildService(TestService.class);
service = controller.attach().create().get();
}
@Test
public void testWithIntent() {
Intent intent = new Intent(RuntimeEnvironment.application, TestService.class);
// add extras to intent
controller.withIntent(intent).startCommand(0, 0);
// assert here
}
@After
public void tearDown() {
controller.destroy();
}
public static class TestService extends MyIntentService {
public boolean enabled = true;
@Override
public void onStart(Intent intent, int startId) {
// same logic as in internal ServiceHandler.handleMessage()
// but runs on same thread as Service
onHandleIntent(intent);
stopSelf(startId);
}
}
}
UPDATE: Alternatively, it's quite straightforward to create a similar controller for IntentService, as follows:
public class IntentServiceController<T extends IntentService> extends ServiceController<T> {
public static <T extends IntentService> IntentServiceController<T> buildIntentService(Class<T> serviceClass) {
try {
return new IntentServiceController<>(Robolectric.getShadowsAdapter(), serviceClass);
} catch (IllegalAccessException | InstantiationException e) {
throw new RuntimeException(e);
}
}
private IntentServiceController(ShadowsAdapter shadowsAdapter, Class<T> serviceClass) throws IllegalAccessException, InstantiationException {
super(shadowsAdapter, serviceClass);
}
@Override
public IntentServiceController<T> withIntent(Intent intent) {
super.withIntent(intent);
return this;
}
@Override
public IntentServiceController<T> attach() {
super.attach();
return this;
}
@Override
public IntentServiceController<T> bind() {
super.bind();
return this;
}
@Override
public IntentServiceController<T> create() {
super.create();
return this;
}
@Override
public IntentServiceController<T> destroy() {
super.destroy();
return this;
}
@Override
public IntentServiceController<T> rebind() {
super.rebind();
return this;
}
@Override
public IntentServiceController<T> startCommand(int flags, int startId) {
super.startCommand(flags, startId);
return this;
}
@Override
public IntentServiceController<T> unbind() {
super.unbind();
return this;
}
public IntentServiceController<T> handleIntent() {
invokeWhilePaused("onHandleIntent", getIntent());
return this;
}
}