Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to test an IntentService with Robolectric?

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 ?

like image 415
Gal Ben-Haim Avatar asked Aug 19 '12 09:08

Gal Ben-Haim


People also ask

What is Robolectric testing?

Robolectric is a framework that brings fast and reliable unit tests to Android. Tests run inside the JVM on your workstation in seconds. With Robolectric you can write tests like this: @RunWith(RobolectricTestRunner.

Is Robolectric deprecated?

Robolectric is intended to be fully compatible with Android's official testing libraries since version 4.0. As such we encourage you to try these new APIs and provide feedback. At some point the Robolectric equivalents will be deprecated and removed.

What is the difference between Mockito and Robolectric?

Mockito is used for mocking the dependency which means if you want to access an real object in test environment then you need to fake it or we can say mock it. Now a days it is very easier to do mocking of the objects with Mockito. Roboelectric is the industry-standard unit testing framework for Android.

What is Robolectric shadow?

Robolectric works by creating a runtime environment that includes the real Android framework code. This means when your tests or code under test calls into the Android framework you get a more realistic experience as for the most part the same code is executed as would be on a real device.


1 Answers

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;
    }
}
like image 100
hidro Avatar answered Oct 15 '22 21:10

hidro