This tutorial describes how to write unit test for Android applications with the Robolectric framework.

1. Robolectric

1.1. What is Robolectric

Robolectric is a framework that allows you to write unit tests and run them on a desktop JVM while still using Android API.

Robolectric provides a JVM compliant version of the android.jar file. Robolectric handles inflation of views, resource loading, and lots of other stuff that’s implemented in native C code on Android devices.

This enables you to run your Android tests in your continuous integration environment without any additional setup. Robolectric supports resource handling, e.g., inflation of views. You can also use the findViewById() to search in a view.

Robolectric is not an integration test framework, i.e., you cannot not test the interaction of Android components with it.

Robolectric does not require additional mocking frameworks, of course it is still possible to use frameworks like Mockito if desired.

1.2. Shadow objects

Robolectric replaced all Android classes by so-called shadow objects. If a method is implemented by Robolectric, it forwards these method calls to the shadow object. Shadow objects behave similar to the Android implementation. If a method is not implemented by the shadow object, it simply returns a default value, e.g., null or 0.

To access a shadow object use Shadows.shadowOf.

2. Using Robolectric for your test with Gradle

To use Robolectric for your Android unit tests, add the following dependency to your Gradle build file.

dependencies {
    ...
    // Robolectric
    testCompile "org.robolectric:robolectric:3.3.2"
}

Your tests should be stored in the src/test directory. The class containing your Robolectric test must be annotate with the @RunWith(RobolectricGradleTestRunner.class) test runner. It must also use the @Config() to point to your BuildConfig.class class. The following shows an example test configured to run via Robolectric on the JVM.

package com.vogella.android.test.robolectric;

import com.vogella.android.test.robolectric.BuildConfig;
import com.vogella.android.test.robolectric.R;
import com.vogella.android.test.robolectric.WelcomeActivity;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;

import static org.junit.Assert.assertNotNull;

@RunWith(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class)

public class WelcomeActivityTest
{
    private WelcomeActivity activity;

    @Before
    public void setUp() throws Exception
    {
        activity = Robolectric.buildActivity( WelcomeActivity.class )
                              .create()
                              .resume()
                              .get();
    }

    @Test
    public void shouldNotBeNull() throws Exception
    {
        assertNotNull( activity );
    }

    @Test
    public void shouldHaveWelcomeFragment() throws Exception
    {
        assertNotNull( activity.getFragmentManager().findFragmentById( R.id.welcome_fragment ) );
    }
}

Instead of specifying the values via the @Config annotation, you could also use a resources/robolectric.properties file. See http://robolectric.org/configuring for more information.

3. Exercise: Using Robolectric and Android Studio to test your activity

This exercise describes how to write a unit test for an Activity using Robolectric and Android Studio.

3.1. Project setup

Create an application called com.vogella.android.robolectric with an activity call RobolectricActivity. Use the com.vogella.android.robolectric top level package name.

To use Robolectric in your application, add the following dependency to your build.gradle file

testImplementation "org.robolectric:robolectric:3.3.2"

3.2. Activity to test

Adjust your activity to the following:

package com.vogella.android.robolectric;

import android.app.Activity;
import android.os.Bundle;

public class RobolectricActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

Adjust the layout file of your activity to the following.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:tools="http://schemas.android.com/tools"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical"
              tools:context="com.vogella.android.robolectric.RobolectricActivity">
    <TextView
        android:id="@+id/hello_textview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="Hello World!"
        android:gravity="center"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"
        android:layout_marginTop="5dp"
        android:layout_marginBottom="5dp"
        android:background="@android:color/holo_green_dark"/>
</LinearLayout>

3.3. Create a unit test for the ativity

Create the following test class and place it in your src/test directory. This class tests, that the TextView has a top and bottom margin of 5dp and a left and right margin of 10dp.

package com.vogella.android.robolectric;

import android.widget.LinearLayout;
import android.widget.TextView;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;

import static org.junit.Assert.assertEquals;

@RunWith(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class)
public class RobolectricActivityTest {

    private RobolectricActivity activity;

    @Before
    public void setUp() throws Exception {
        activity = Robolectric.buildActivity(RobolectricActivity.class)
                .create()
                .resume()
                .get();
    }

    @Test
    public void shouldHaveDefaultMargin() throws Exception {
        TextView textView = (TextView) activity.findViewById(R.id.hello_textview);
        int bottomMargin = ((LinearLayout.LayoutParams) textView.getLayoutParams()).bottomMargin;
        assertEquals(5, bottomMargin);
        int topMargin = ((LinearLayout.LayoutParams) textView.getLayoutParams()).topMargin;
        assertEquals(5, topMargin);
        int rightMargin = ((LinearLayout.LayoutParams) textView.getLayoutParams()).rightMargin;
        assertEquals(10, rightMargin);
        int leftMargin = ((LinearLayout.LayoutParams) textView.getLayoutParams()).leftMargin;
        assertEquals(10, leftMargin);
    }
}

A method with a @Before annotation creates the RobolectricActivity. A method which is annotated with @Test is an actual test method and represents a single JUnit test case.

3.4. Create the run configuration

Now create a run configuration to run your unit test. To do so, select Run and then Edit Configurations…​. Use the green + sign at the top left corner to add a new run configuration and select Android JUnit. Make your JUnit run configuration look like the following:

run configuration

At Use classpath of module select the app module of your Android application. As Working directory use the directory of your app module. Class is the file which contains your unit tests (in this case your ctivityTest class).

Now you can simply select and run this run configuration to run your unit tests.

3.5. Extend the tests

Extend your activity and your test so that the following tests are successful.

package com.vogella.android.robolectric;

import android.content.Intent;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.Shadows;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowToast;

import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;

@RunWith(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class)
public class RobolectricActivityTest {

    private RobolectricActivity activity;

    @Before
    public void setUp() throws Exception {
        activity = Robolectric.buildActivity(RobolectricActivity.class)
                .create()
                .resume()
                .get();
    }

    @Test
    public void shouldHaveDefaultMargin() throws Exception {
        TextView textView = (TextView) activity.findViewById(R.id.hello_textview);
        int bottomMargin = ((LinearLayout.LayoutParams) textView.getLayoutParams()).bottomMargin;
        assertEquals(5, bottomMargin);
        int topMargin = ((LinearLayout.LayoutParams) textView.getLayoutParams()).topMargin;
        assertEquals(5, topMargin);
        int rightMargin = ((LinearLayout.LayoutParams) textView.getLayoutParams()).rightMargin;
        assertEquals(10, rightMargin);
        int leftMargin = ((LinearLayout.LayoutParams) textView.getLayoutParams()).leftMargin;
        assertEquals(10, leftMargin);
    }

    @Test
    public void checkActivityNotNull() throws Exception {
        assertNotNull(activity);
    }

    @Test
    public void shouldHaveCorrectAppName() throws Exception {
        String hello = activity.getResources().getString(R.string.app_name);
        assertThat(hello, equalTo("Hello world!"));
    }

    @Test
    public void buttonClickShouldStartNewActivity() throws Exception
    {
        Button button = (Button) activity.findViewById( R.id.startNextActivity );
        button.performClick();
        Intent intent = Shadows.shadowOf(activity).peekNextStartedActivity();
        assertEquals(RobolectricSecondActivity.class.getCanonicalName(), intent.getComponent().getClassName());
    }

    @Test
    public void testButtonClickShouldShowToast() throws Exception {
        RobolectricActivity activity = Robolectric.buildActivity(RobolectricActivity.class).create().get();
        Button view = (Button) activity.findViewById(R.id.showToast);
        assertNotNull(view);
        view.performClick();
        assertThat(ShadowToast.getTextOfLatestToast(), equalTo("Lala") );
    }

}

4. Robolectric resources