Mobile
Article
By Abbas Suterwala

How To Write Tests For Android Development

By Abbas Suterwala

Automated unit tests for an Android app are necessary for its long-term quality. Unit tests help to test one unit of your code (for example a class). This helps in catching and identifying bugs or regressions very early in the development cycle. In this article, we are going to see how we can write unit tests for our Android app. In Android, Unit Tests can be of two types:

  • Local unit tests – which run on the development machine itself not on an actual machine
  • Instrumented unit test – which runs on an actual Android device.

Creating Unit tests with JUnit and Mocking objects with Mockito

We will start by creating local unit tests for an Android app that adds two numbers.
The app contains the following helper class

package com.testsinandroid;

import android.support.annotation.VisibleForTesting;


public class NumberAdder {

       private final MainActivity mMainActivity;
       public NumberAdder(MainActivity activity) {
            mMainActivity = activity;
        }

    public void performAddition() {

        double number1 = mMainActivity.getFirstNumber();
        double number2 = mMainActivity.getSecondNumber();

        if(!isNumberValid(number1) || !isNumberValid(number2)) {
            throw new RuntimeException("invalid numbers");
        }

        double result = number1 + number2;

        mMainActivity.setAdditionResult(result);
    }

    @VisibleForTesting
    boolean isNumberValid(double number) {
        if(number > 0) {
            return true;
        } else {
            return false;
        }
    }
}

And also contains the activity which uses this class

package com.testsinandroid;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;


public class MainActivity extends Activity {


    EditText firstNumber;
    EditText secondNumber;
    TextView addResult;
    Button btnAdd;

    NumberAdder numberAdder = null;


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

        firstNumber = (EditText)findViewById(R.id.txtNumber1);
        secondNumber = (EditText)findViewById(R.id.txtNumber2);
        addResult = (TextView)findViewById(R.id.txtResult);
        btnAdd = (Button)findViewById(R.id.btnAdd);

        if(numberAdder == null) {
            numberAdder = new NumberAdder(this);
        }

        btnAdd.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                numberAdder.performAddition();
            }
        });


    }

    public double getFirstNumber() {
        return Double.parseDouble(firstNumber.getText().toString());
    }

    public double getSecondNumber() {
        return Double.parseDouble(secondNumber.getText().toString());
    }

    public void setAdditionResult(double result) {
        addResult.setText(Double.toString(result));
    }

}

The activity layout is as follows

<RelativeLayout 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:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">

    <EditText
        android:id="@+id/txtNumber1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:ems="2"
        android:inputType="number" >

        <requestFocus />
    </EditText>

    <EditText
        android:id="@+id/txtNumber2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/txtNumber1"
        android:ems="2"
        android:inputType="number" >

    </EditText>

    <Button
        android:id="@+id/btnAdd"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="46dp"
        android:layout_below="@+id/txtNumber2"
        android:text="Add" />

    <TextView
        android:id="@+id/txtResult"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/btnAdd"
        android:textAppearance="?android:attr/textAppearanceMedium" />
</RelativeLayout>

Once we have our app ready, we now have two main units to test in our code

  • NumberAdder – This is a plain Java class which takes the MainActivity as a dependency.
  • MainActivity – The activity which shows the UI.

As NumberAdder is a plain Java class, one can use Junit to test such classes.
JUnit is a simple framework to write repeatable unit tests. When we are testing one unit (NumberAdder in this case) all other classes which NumberAdder depends on can be mocked out. A good framework for mocking out dependencies in Java is Mockito. To add JUnit and Mockito as test dependencies in our project add the following to build.gradle

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:25.0.1'


    // Required -- JUnit 4 framework
    testCompile 'junit:junit:4.12'
    testCompile 'org.mockito:mockito-core:1.10.19'


}

To write a Junit + Mockito test for NumberAdder create a file NumberAdderTest.java in the folder
src/test/java/com/testsinandroid with the following content

package com.testsinandroid;
import com.testsinandroid.MainActivity;
import com.testsinandroid.NumberAdder;

import org.junit.Test;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.class)
public class NumberAdderTest {

    @Mock
    MainActivity mMockMainActivity;

    @Test
    public void testIsNumberValid() {
        //setup

        //test
        NumberAdder numberAdder = new NumberAdder(mMockMainActivity);
        assert(numberAdder.isNumberValid(55.0));

    }

    @Test
    public void testIsNumberNotValid() {
        //setup

        //test
        NumberAdder numberAdder = new NumberAdder(mMockMainActivity);
        assertFalse(numberAdder.isNumberValid(-55.0));

    }

    @Test
    public void testPerformAddition() {
        //setup
        when(mMockMainActivity.getFirstNumber())
                .thenReturn(10.0);
        when(mMockMainActivity.getSecondNumber())
                .thenReturn(11.0);

        //test
        NumberAdder numberAdder = new NumberAdder(mMockMainActivity);
        numberAdder.performAddition();

        //verify
        verify(mMockMainActivity).setAdditionResult(21.0);

    }

}

The above code creates three tests, which are annotated with the @Test annotation. The test is run using MockitoJUnitRunner. This runner injects a mock object for each field annotated with @Mock. MainActivity is mocked in the above test. In the first two tests, we are doing an assert based on the value returned from the function. In the third test, we set up the values that are to be returned when getFirstNumber and getSecondNumber are called. Then we verify if the method setAdditionResult with the correct value on the mMockMainActivity.

Once we have written these local JUnit tests we can run them using the gradle command. The build will be successful if all the tests pass or fail if they don’t.

./gradlew test

Write Tests using Roboelectric

Robolectric is a Unit testing framework for Android. With Roboelectric you can run the android unit test on JVM on your workstation. This becomes very handy to unit test your Android code on your workstation. One can fail the build if tests are not passed.

To add Roboelectric to your test dependencies, update your dependencies as follows

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:25.0.1'


    // Add Junit and mockito as testing dependencies
    testCompile 'junit:junit:4.12'
    testCompile 'org.mockito:mockito-core:1.10.19'

    //For Roboelectric
    testCompile "org.robolectric:robolectric:3.0"
}

Once we have added the dependency, we can add the following test for MainActivity

package com.testsinandroid;


import org.junit.Test;
import org.junit.Before;

import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.annotation.Config;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import static org.junit.Assert.*;

import static org.junit.Assert.assertTrue;
import org.robolectric.RobolectricGradleTestRunner;

@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21)
public class MainActivityTest {

    MainActivity activity;
    EditText firstNumber;
    EditText secondNumber;
    TextView addResult;
    Button btnAdd;

    @Before
    public void setUp() {
        activity = Robolectric.setupActivity(MainActivity.class);
        firstNumber = (EditText)activity.findViewById(R.id.txtNumber1);
        secondNumber = (EditText)activity.findViewById(R.id.txtNumber2);
        addResult = (TextView)activity.findViewById(R.id.txtResult);
        btnAdd = (Button) activity.findViewById(R.id.btnAdd);
    }

    @Test
    public void testMainActivityAddition() {
        //setup 

        firstNumber.setText("12.2");
        secondNumber.setText("13.3");

        //test
        btnAdd.performClick();

        //verify
        assertEquals(25.5, Double.parseDouble(addResult.getText().toString()), 0.0);

    }
}

In the above test, we create the MainActivityTest. It is run it with the RobolectricGradleTestRunner.
Then we use the Roboelectric API ‘Robolectric.setupActivity’ which instantiates the activity. It also calls its lifecycle methods like ‘OnCreate’, ‘onStart’ etc. Then we get the different view elements in the activity and set the values. Once the values are set we perform a click on the button. Then we finally verify that the result TextView has the appropriate value by using ‘assertEquals’.

As shown in the above example Roboelectric makes it easy to write a unit test for your Android code.

--ADVERTISEMENT--

Writing Android Instrumentation tests

Android lets you write instrumentation unit tests. These are tests which run on an actual Android device. As you are testing on the actual device you don’t need to mock the Android classes. Testing the android components like activity etc get easy with an intrumentation test. But the problem is that you need an actual device to run the test. These cannot run it on your workstation.

To add instrumentation update your Gradle dependencies as follows

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:25.0.1'


    // Add Junit and mockito as testing dependencies
    testCompile 'junit:junit:4.12'
    testCompile 'org.mockito:mockito-core:1.10.19'

    //For Roboelectric
    testCompile "org.robolectric:robolectric:3.0"

    //For Instrumentation tests

    androidTestCompile 'com.android.support:support-annotations:25.0.1'
    androidTestCompile 'com.android.support.test:runner:0.5'
    androidTestCompile 'com.android.support.test:rules:0.5'
}

To write an intrumentation test for ‘MainActivity’ create a file MainActivityIntrumentationTest.java. The file should be in the folder
src/androidTest/java/com/testsinandroid with the following content

package com.testsinandroid;


import android.app.Instrumentation;
import android.test.ActivityInstrumentationTestCase2;
import android.test.TouchUtils;
import android.test.ViewAsserts;
import android.view.View;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import static org.junit.Assert.*;
import android.test.UiThreadTest; 



public class MainActivityIntrumentationTest extends ActivityInstrumentationTestCase2<MainActivity> {
    MainActivity activity;
    EditText firstNumber;
    EditText secondNumber;
    TextView addResult;
    Button btnAdd;

  public MainActivityIntrumentationTest() {
    super(MainActivity.class);
  }


  @Override
  protected void setUp() throws Exception {
    super.setUp();

    setActivityInitialTouchMode(true);

    activity = getActivity();
    firstNumber = (EditText)activity.findViewById(R.id.txtNumber1);
    secondNumber = (EditText)activity.findViewById(R.id.txtNumber2);
    addResult = (TextView)activity.findViewById(R.id.txtResult);
    btnAdd = (Button) activity.findViewById(R.id.btnAdd);
  }

   @UiThreadTest
   public void testMainActivityAddition() {

        //setup 
        firstNumber.setText("12.2");
        secondNumber.setText("13.3");

        //test
        btnAdd.performClick();

        //verify
        assertEquals(25.5, Double.parseDouble(addResult.getText().toString()), 0.0);

   }

}

In the above code, our test class inherits from ActivityInstrumentationTestCase2. ActivityInstrumentationTestCase2 lets us write test for Activity. In the constructor of this class, we should pass the activity under test (‘MainActivity’ in our case). Then we override the ‘setUp’ method in which we get the activity object and other UI elements of the activity. Then we write a test which is similar to the one we wrote in the previous section. The test has an annotation ‘@UiThreadTest’. This will make this test run on UI thread as we have some UI operations in the test.
We can run the instrumentation test after connecting a device using the command

./gradlew connectedCheck

Conclusion

In this article, we have seen many ways of writing unit tests for your Android code. Depending upon your needs for the project you can have one or many types of above tests in your project. Automated unit tests have a lot of long-term benefits. They should be thought of and estimated as the part of your development effort itself. The above-described frameworks help to write unit tests for your android app. They do the most of the heavy lifting under the hood for you so you can focus on the test. So have fun writing unit tests in you next Android app.

More:
Recommended
Sponsors
The most important and interesting stories in tech. Straight to your inbox, daily. Get Versioning.
Login or Create Account to Comment
Login Create Account