In this tutorial, we’ll discuss Android Unit testing which forms an integral part of Android Application Development. We’ll specifically implement local Unit Testing using JUnit4.

Android Unit Testing

As the name says, Unit testing is testing every unit of your code.

Unit testing is a must to build robust applications. It is an important element while building quality applications.

Unit testing consists of test cases which are used to check the business logic of your code.
Many times when you have been asked to or plan to add a feature in a working application just to realize that it broke some other part of your code. It’s too much to perform all the tests manually everytime when you refactor your code or add new things in it. This is where Unit Testing comes to our rescue.

It performs quick automatic tests and alerts you if any test failed. You can quickly identify the issue.

In general Testing is largely divided into the following types:

  • Unit Tests
  • Integration Tests
  • UI Tests

Unit tests are the smallest (individually) and with the least execution time.

Following is an illustration quantifying each of the testing types from the Google Docs:

android-unit-testing-docs (1)

Following are some of the testing frameworks used in Android:

  • JUnit
  • Mockito
  • Powermock
  • Robolectric
  • Espresso
  • Hamcrest

Whenever you start a new Android Studio Project, JUnit dependency is already present in the build.gradle(also Expresso Dependency).

android-unit-testing-dependency (1)

In your Android Studio Project, the following are the three important packages inside the src folder:

  • app/src/main/java/ —  Main java source code folder.
  • app/src/test/java/ —  Local unit test folder.
  • app/src/androidTest/java/ — Instrumentation test folder.

test folder is where the JUnit4 test cases will be written.

Local Unit Testing cannot have Android APIs.
The test folder classes are compiled and run on the JVM only.

The instrumentation tests are run on the Android device or emulator.

To create tests, we need to either extend the class with TestCase or add the annotation @Test above the methods. TestCase was used mainly till JUnit3. Going forward, we just to set the annotation.

Let’s create a new Android Studio Project in which we’ll write our first Unit Tests.

In the following section, we have created a basic application in which we will check whether the string is a valid Email Address or not. For this, we’ll create an EditText in our Activity as well.

By writing Unit Tests, we’ll understand how to improve the application logic as well by covering the various end conditions.

Project Structure

android-unit-junit-project-structure (1)

 

Code

Let’s code for the activity_main.xml. layout is given below:


<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:app="https://schemas.android.com/apk/res-auto"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <EditText
        android:id="@+id/inEmail"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:inputType="textEmailAddress"
        android:text="Enter your email here"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:text="CHECK"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/inEmail" />
</android.support.constraint.ConstraintLayout>

We’ll look at the MainActivity.java code later after we’ve built our test cases and done a TDD(Test Driven Development).

The code inside the Utils.java class is:


package com.journaldev.androidunittestingjunit4;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Utils {
    private static final int MILLIS = 1000;
    public static boolean checkEmailForValidity(String email) {
        Matcher matcher = VALID_EMAIL_ADDRESS_REGEX.matcher(email);
        return matcher.find();
    }
    private static final Pattern VALID_EMAIL_ADDRESS_REGEX =
            Pattern.compile("^[A-Z0-9._%+-][email protected][A-Z0-9.-]+\.[A-Z]{2,6}$", Pattern.CASE_INSENSITIVE);
    public static Date calendarDate(long epocSeconds) {
        Calendar c = Calendar.
                getInstance(TimeZone.getTimeZone("UTC"));
        c.setTimeInMillis(epocSeconds * MILLIS);
        return c.getTime();
    }
}

Now let’s write our Unit Tests for both of the methods: checkEmailForValidity and calendarDate in the test/java folder.

Unit Test Case 1

Create a new java file UtilsTest.java and add the following code:


package com.journaldev.androidunittestingjunit4;
import org.junit.Assert;
import org.junit.Test;
import java.util.Date;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
public class UtilsTest {
    @Test
    public void testIsEmailValid() {
        String testEmail = "[email protected]";
        Assert.assertThat(String.format("Email Validity Test failed for %s ", testEmail), Utils.checkEmailForValidity(testEmail), is(true));
    }
    @Test
    public void testCheckDateWasConvertedCorrectly() {
        long inMillis = System.currentTimeMillis();
        Date date = Utils.calendarDate(inMillis);
        assertEquals("Date time in millis is wrong",
                inMillis * 100, date.getTime());
    }
}

In the first test, we call the method checkEmailForValidity defined in the main/java folder. We pass a test string to check for the validity inside the assertThat method.

In the second test case, we purposely convert the timeInMillis into time in seconds incorrectly by multiplying it by 100 instead of 1000. Here we use assertEquals function.

You can run the unit test methods by gradle or click the run icon beside them.
Using gradle just execute the command gradlew test from the terminal in Android Studio.

Let’s look at the output when each of the methods is run.

android-unit-junit-output-2 (1)

Besides the above two assert methods there are plenty more:

android-unit-junit-output-2 (1)

It looks like assertThat and assertEquals have a similar method definition. Both have an optional first argument which is the message displayed when the test fails, followed by expected and actual value.

Ironically, assertThat and assertEquals are quite different from each other.

assertThat vs assertEquals

assertThat incorporates the Hamcrest Library which improves the readability of the code. The Hamcrest Library consists of static methods which are known as matchers.

Let’s compare the syntax of the two methods:


assertEquals(expected, actual);
assertThat(actual, is(equalTo(expected)));

assertThat has the actual value first. Thanks to the is method, it improves the readability. In the assertEquals method, you can easily get confused and interchange the actual and expected argument position.

AssertThat is type safe and short and concise.
Example: Assume foo is an object instance in the below code


assertTrue(foo.contains("someValue") && foo.contains("anotherValue"));

The same thing when written with assertThat becomes:


assertThat(foo, hasItems("someValue", "anotherValue"));

Hence assertThat should be the preffered method over the other methods.

Back onto our application, let’s add another Test Case.

Unit Test Case 2


@Test
    public void testEmailValidityPartTwo() {
        String testEmail = "   [email protected]  ";
        Assert.assertThat(String.format("Email Validity Test failed for %s ", testEmail), Utils.checkEmailForValidity(testEmail), is(true));
    }

Here we’ve added whitespacing besides the test string. Obviously this would fail.

android-unit-junit-output-2 (1)

That reminds us to trim the white spacing in the checkEmailForValidity method. We can set the trim() method on the string in the Utils.java class.


public static boolean checkEmailForValidity(String email) {
        email = email.trim();
        Matcher matcher = VALID_EMAIL_ADDRESS_REGEX.matcher(email);
        return matcher.find();
    }

Your MainActivity.java code looks like this:


package com.journaldev.androidunittestingjunit4;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final EditText editText = findViewById(R.id.inEmail);
        Button button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                boolean isValid = Utils.checkEmailForValidity(editText.getText().toString());
                if (isValid) {
                    Toast.makeText(getApplicationContext(), "Email is valid", Toast.LENGTH_LONG).show();
                } else {
                    Toast.makeText(getApplicationContext(), "Email not valid", Toast.LENGTH_LONG).show();
                }
            }
        });
    }
}

Instead of running your application to test whether the email is valid or not, we can simply just run the JVM tests we wrote before.

This brings an end to this tutorial. We’ve added two more test cases for null/empty string in the UtilsTest.java file. You can find that in the source code below:

By admin

Leave a Reply

%d bloggers like this: