How To Write Tests For Android Development

    Abbas Suterwala
    Share

    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.

    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.

    CSS Master, 3rd Edition