Android — How to Unit test a Service

4 min read
Recently i worked on a project where i had to test a service, and id like to share with you all how to do so easily. The android dos explain this as well HERE but they forget to mention a few key points.

The project i was working on was to initialize an SDK from a third party app, when the SDK was initialized, by the time all of the initialization was complete, there were 2 services that should’ve been started properly.

Here is how i tested this without using the approach mentioned in the google documentation mentioned above.

After the SDK was initiated, inside of init method a few things occurred and one of them was to call into the starServices method.

public class TheSDK {

    private static void init(@NonNull final Context context) {

        // .....

        startServices(context);
        
        // .....
    }

    private static void startServices(final @NonNull Context context) {
        context.startService(new Intent(context, Service1.class));
        context.startService(new Intent(context, service2.class));
    }

    public static class Builder {
           
           // .....

    public void init() {
            TheSDK.init(...);
        }
    }
}

As you can see above 2 services were stared. I will only show the test for one of them since it is the same thing for both.

Here is what the service looks like…

public class Service1 extends Service {

    @RestrictTo (RestrictTo.Scope.TESTS)
    static boolean started;

    private SomeBroadCastReceiver receiver;

    public Service1() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        throw null;
    }

    @Override
    public int onStartCommand(final Intent intent, final int flags, final int startId) {
        return START_STICKY;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        started = true;
        //...init broadcast receiver and whatnot
    }

    @Override
    public void onDestroy() {
        unregisterReceiver(receiver);
        super.onDestroy();
    }
}

The most important part in this whole receiver is this:

@RestrictTo (RestrictTo.Scope.TESTS)
static boolean started;

and in this line inside the onCreate

started = true;

These 3 lines are very straight forward. A variable is created to represent if the service was started or not, and it is set to true in the Service onCreate method. The line above the “started” boolean declaration tells us that the boolean is restricted to TESTS only. Production code should not use this.

The test is even easier. First of all make sure that your test is an android test. Meaning put it under androidTest instead of test.

                                                   Put test in the androidTest directory
@RunWith (AndroidJUnit4.class) // <<----Make sure you add this!!!!
public class TheSDKTest {

    @Test
    public void test_services_started_on_init() throws TimeoutException {
        final Context context = InstrumentationRegistry.getTargetContext();

        new TheSDK.Builder(context").init();

        assertTrue(Service1.started);
        assertTrue(Service2.started);
    }
}

Note: Make sure you add @RunWith (AndroidJUnit4.class) above the test class.

How easy was that?!

Oh and BTW, these test run on your devices, you will need a device connected in order to run the test. These tests are called instrumentation tests. Instrumentation tests require the Android OS (to run on a phone or emulator). Whereas a unit test just runs on the JVM/local on your IDE.

Note: nowhere in this post did i mention to add the services to the manifest, so make sure that the services are added to the manifest accordingly.


Extra Tip

A quick tip to create a test is to put the cursor on top of any class name, lets say the main activity like so

You see that lightbulb there!!! ^^….click on it!!!

Once you do so you will the the menu above. Click on “Create test” and then you will see the following popup dialog

Once you click OK, you will be asked where the test class should be created. Select the “androidTest” destination and boom. You are on to writing your tests.


EDIT

I have made some changes to the actual service classes where they now extend a base service class “MyService” where all the isStartedLogic is handled abstracted from the services themselves.

Here is the MyService class

class MyService extends Service {

    @RestrictTo (RestrictTo.Scope.TESTS)
    private static boolean isServiceStarted;

    @Override
    public IBinder onBind(Intent intent) {
        throw null;
    }

    @Override
    public int onStartCommand(final Intent intent, final int flags, final int startId) {
        return START_STICKY;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        isServiceStarted = true;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        isServiceStarted = false;
    }

    public static boolean isServiceStarted() {
        return isServiceStarted;
    }

}

Notice how i also handle onBind and onStartCommand in the MyService class, this is because these are similar for the services that extend this class. This may differ depending on your specific use case.

Now the previous service looks like this after extending MyService

public class Service1 extends MyService {

    private SomeBroadCastReceiver receiver;

    public Service1() {
    }

    @Override
    public void onCreate() {
        super.onCreate();
        //...init broadcast receiver and whatnot
    }

    @Override
    public void onDestroy() {
        unregisterReceiver(receiver);
        super.onDestroy();
    }
}

And finally the previous test now looks like this:

@RunWith (AndroidJUnit4.class) // <<----Make sure you add this!!!!
public class TheSDKTest {

    @Test
    public void test_services_started_on_init() throws TimeoutException {
        final Context context = InstrumentationRegistry.getTargetContext();

        new TheSDK.Builder(context").init();

        assertTrue(Service1.isServiceStarted());
        assertTrue(Service2.isServiceStarted());
    }
}

And since we now have another class (MyService), theres a very simple instrumentation test that goes along with it that looks like this

@RunWith (AndroidJUnit4.class)
public class MyService {

    private Context context;

    @Before
    public void setUp() {
        context = InstrumentationRegistry.getTargetContext();
    }

    @Test (expected = NullPointerException.class)
    public void onBind() throws Exception {
        final MyService myService = new MyService();
        context.startService(new Intent(context, MyService.class));

        myService.onBind(new Intent());
    }

    @Test
    public void onStartCommand() throws Exception {
        final MyService myService = new MyService();
        context.startService(new Intent(context, MyService.class));

        assertEquals(Service.START_STICKY, myService.onStartCommand(new Intent(), 0, 0));
    }

    @Test
    public void onCreate() throws Exception {
        final MyService myService = new MyService();
        context.startService(new Intent(context, MyService.class));

        myService.onCreate();
        assertTrue(MyService.isServiceStarted());
    }

    @Test
    public void onDestroy() throws Exception {
        final MyService myService = new MyService();
        context.startService(new Intent(context, MyService.class));

        myService.onDestroy();
        assertFalse(MyService.isServiceStarted());
    }
}

Until next time!