Android Mocking

One of the major goals whether it’s on the Android platform or not is to isolate the code that we’re testing. When we write our tests we should be testing a single method and not its dependencies such as a web service. Mocking out these third party interactions is a great way to help us ring fence a method so we’re not reliant on such things as the network or a device’s location or US or UK time when we’re doing our testing. A test should fail because there’s something wrong with the code and not because the wifi isn’t working.

But there’s another major Android reason why we want to use Mocking frameworks and that’s because we want all our tests to be @SmallTests, i.e. don’t require an emulator. Mocking dependencies allows you to get your tests to run orders of magnitude quicker than the dreaded alternative which is to wait a couple minutes for the emulator to start. Sure there are times when you need to use an emulator such as when you’re testing Activities – see later Chapter on Espresso – but if you’re not testing Activities then mocking will give us the confidence to annotate our tests as @SmallTest.

In this post we’ll look at using Mockito to mock out the following interactions for both test isolation and faster test execution.

  • Shared Preferences
  • Time
  • Settings
  • SQLite Databases

We’ve also already covered web services briefly in an earlier post.

Shared Preferences

Shared preferences are typically stored as xml files on the device in the /data/data/ folder. Under normal circumstances any testing that requires file system access means we’re going to have to use an Emulator unless we use Mockito.

In our example to show how this work, we’re going to use a simple login app. It doesn’t do much other than let you login with a username, password and email address and then displays the information on the second page, see figure 1.

Figure 1. Registration App.

In our fake app we want to show that the user has already registered, so the first time the user logs in we’re going to write to the app’s shared preferences. The code for writing to the shared preferences file is shown in Listing 1. The method takes an Activity and a string as its parameters. The complete code is available at http://github.com/godfreynolan/AgileAndroid

1
2
3
4
public void saveSharedPreferences(Activity activity, String spValue) {
    SharedPreferences preferences = activity.getPreferences(Activity.MODE_PRIVATE);
    preferences.edit().putString(SHAREDPREF, spValue).apply();
}

Listing 1. Saving to the shared preferences.

Listing 2 shows the call to check to see the value stored in our shared preferences.

1
2
3
4
5
public String getSharedPreferences(Activity activity) {
    SharedPreferences preferences = activity
            .getPreferences(Activity.MODE_PRIVATE);
    return preferences.getString(SHAREDPREF, "Not registered");
}

Listing 2. Reading from shared preferences.

Run the app on the Android emulator and enter your login credentials. You can see what’s stored in the shared preferences, by running the adb shell command on the emulator, see Listing 3. It will also work on a rooted device.

1
2
3
4
5
6
7
8
9
>adb shell
root@generic:/ # cd /data/data/com.riis.hellopreferences/shared_prefs
root@generic:/data/data/com.riis.hellopreferences/shared_prefs # ls
MainActivity.xml
root@generic:/data/data/com.riis.hellopreferences/shared_prefs # cat MainActivity.xml
<!--?xml version='1.0' encoding='utf-8' standalone='yes' ?-->
<map>
    <string name="registered">true</string>
</map>

Listing 3. Login app’s shared preference

Shared preferences is built in Android functionality, meaning that we don’t need to test it. In a real app we may want to test our code assuming that the user is already registered when the app is under test. Listing 4 shows the mocked out call for the getSharedPreferences method, sharedPreferencesTest_ReturnsTrue.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@RunWith(MockitoJUnitRunner.class)
public class UserPreferencesTest {
    public UserPreferences tUserPreferences = Mockito.mock(UserPreferences.class);
    private Activity tActivity;
    @Before
    public void setUp() {
        Mockito.when(tUserPreferences.getSharedPreferences(tActivity)).thenReturn("true");
    }
    @Test
    public void sharedPreferencesTest_ReturnsTrue() {
        Assert.assertThat(tUserPreferences.getSharedPreferences(tActivity), is("true"));
    }
}

Listing 4. Mocked getSharedPreferences.

sharedPreferencesTest_ReturnsTrue always returns true so we can bypass the shared preferences and get on with what’s important in our testing.

Time

Taking advantage of interfaces can be a very useful mocking technique. For example if we have a Clock interface that calls a Clock implementation class which tells the time, then we use Mockito to mock the interface Clock class to provide our own Android date/time environment. The interface abstraction allows us to hide the implementation so we can have complete control over time zones and time of day and create a lot more edge case tests to really work our code.

Listing 5 shows the Clock interface code.

1
2
3
4
5
import java.util.Date;
public interface Clock {
    Date getDate();
}

Listing 5. Clock interface

Listing 6 shows the Clock implementation code.

1
2
3
4
5
6
7
8
import java.util.Date;
public class ClockImpl implements Clock {
    @Override
    public Date getDate() {
        return new Date();
    }
}

Listing 6. ClockImpl code

The concept here is just like the Shared Preferences we don’t have to test any java.util.Date functionality, we want to test only the code we write that. Listing 7 has a couple of simple methods that double and triple the time in milliseconds.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class TimeChange {
    private final Clock dateTime;
    public TimeChange(final Clock dateTime) {
      this.dateTime = dateTime;
    }
    public long getDoubleTime(){
      return dateTime.getDate().getTime()*2;
    }
    public long getTripleTime(){
      return dateTime.getDate().getTime()*3;
    }
}

Listing 7. Timechange code

In our testing code, see Listing 8, we mock out the Clock and the java.util.Date classes which allows us to set the time to whatever value we want and run some asserts to make sure our doubleTime and tripleTime methods are behaving as expected.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@RunWith(MockitoJUnitRunner.class)
public class TimeChangeTest { 
    private TimeChange timeChangeTest;
    @Before
    public void setUp() {
      final Date date = Mockito.mock(Date.class);
      Mockito.when(date.getTime()).thenReturn(10L);
      final Clock dt = Mockito.mock(Clock.class);
      Mockito.when(dt.getDate()).thenReturn(date);
      timeChangeTest = new TimeChange(dt);
    }
    @Test
    public void timeTest() {
      final long doubleTime = timeChangeTest.getDoubleTime();
      final long tripleTime = timeChangeTest.getTripleTime();
      assertEquals(20, doubleTime);
      assertEquals(30, tripleTime);
    }
}

Listing 8. TimeChangeTest code

System Properties

If we want to avoid using the emulator for testing we need to fake any Java or built in Android functionality. As before in most cases this is exactly what we’re looking for as we’ve seen in the previous example we’re not testing the shared preferences functionality or the Date functionality. Similarly we don’t want to test Android settings, such as the Audio Manager.

Our AudioHelper code has a single methods, maximizeVolume. Listing 9 shows our code to max out the volume.

1
2
3
4
5
6
7
8
import android.media.AudioManager;
public class AudioHelper {
    public void maximizeVolume(AudioManager audioManager) {
      int max = audioManager.getStreamMaxVolume(AudioManager.STREAM_RING);
      audioManager.setStreamVolume(AudioManager.STREAM_RING, max, 0);
    }
}

Listing 9. Testing the max-min limits of our code

Listing 10 shows our test code to set the ringer to the max volume.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
 * Unit tests for the AudioManager logic.
 */
@SmallTest
public class AudioHelperTest {
    private final int MAX_VOLUME = 100;
    @Test
    public void maximizeVolume_Maximizes_Volume() {
      // Create an AudioManager object using Mockito
      AudioManager audioManager = Mockito.mock(AudioManager.class);
      // Inform Mockito what to return when audioManager.getStreamMaxVolume is called
      Mockito.when(audioManager.getStreamMaxVolume(AudioManager.STREAM_RING)).thenReturn(MAX_VOLUME);
      new AudioHelper().maximizeVolume(audioManager);
      //verify with Mockito that setStreamVolume to 100 was called.
      Mockito.verify(audioManager).setStreamVolume(AudioManager.STREAM_RING, MAX_VOLUME, 0);
    }
}

Listing 10. Max volume

We create the mock AudioManager object, and tell our test code to return the MaxVolume when we make the call and then we verify that the Mockito set the volume to our max when then call was made.

Database

Shared preferences are great for storing parameters, URLs or API keys to third party libraries but not so good for large amounts of tabular data. If you do have a lot of spreadsheet type data in Android that you want to keep on the phone then it’s more common to use SQLite databases for storage as it’s free, lightweight and does a great job with 10’s to 1000’s of rows of data. If you need to upgrade to something bigger datasets then you’re much more likely to store that on a backend server than on the device itself.

Using our sample app, see Figure 1 again, we can add the username and email to a SQLite database. To write to the SQLite database you need SQLHelper code, see Listing 11. This is typical boilerplate code used for Android SQLite applications. It creates and upgrades the database and its tables. In this case the Users table have a column for an auto generated id as well as the user’s name and email address.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
public class SQLHelper extends SQLiteOpenHelper {
  private static final int DATABASE_VERSION = 1;
  private static final String DATABASE_NAME = "UserDb";
  private static final String TABLE_USERS = "Users";
  private static final String KEY_ID = "id";
  private static final String KEY_FIRST_NAME = "firstName";
  private static final String KEY_LAST_NAME = "lastName";
  private static final String[] COLUMNS = {KEY_ID, KEY_FIRST_NAME, KEY_LAST_NAME};
  public SQLHelper(Context context) {
      super(context, DATABASE_NAME, null, DATABASE_VERSION);
  }
  @Override
  public void onCreate(SQLiteDatabase db) {
      String CREATE_USER_TABLE = "CREATE TABLE Users ( " +
            "id INTEGER PRIMARY KEY AUTOINCREMENT, " +
            "firstName TEXT, "+
            "lastName TEXT )";
      db.execSQL(CREATE_USER_TABLE);
  }
  @Override
  public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
      db.execSQL("DROP TABLE IF EXISTS Users");
      this.onCreate(db);
  }
  public void addUser(User user){
      SQLiteDatabase db = this.getWritableDatabase();
      ContentValues values = new ContentValues();
      values.put(KEY_FIRST_NAME, user.getFirstName());
      values.put(KEY_LAST_NAME, user.getLastName());
      db.insert(TABLE_USERS, null, values);
      db.close();
  }
  public User getUser(int id){
      SQLiteDatabase db = this.getReadableDatabase();
      Cursor cursor = db.query(TABLE_USERS, COLUMNS, " id = ?"new String[] { String.valueOf(id) }, nullnullnullnull);
      if (cursor != null) {
          cursor.moveToFirst();
      }
      User user = new User();
      user.setId(Integer.parseInt(cursor.getString(0)));
      user.setFirstName(cursor.getString(1));
      user.setLastName(cursor.getString(2));
      return user;
  }
}

Listing 11. SQLite code to create user database

In the past, developers have isolated their databases during testing by using an in memory SQLite database. You can do this by leaving the DATABASE_NAME as null, i.e. super(context, null, null, DATABASE_VERSION); Unfortunately this won’t work for us as it still requires an emulator so we’re going to have to rely on our mocking.

Listing 12 shows the UserOperations code that we want to test, this is our create, read, update, delete or CRUD code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
public class UserOperations {
  private DataBaseWrapper dbHelper;
  private String[] USER_TABLE_COLUMNS = { DataBaseWrapper.USER_ID, DataBaseWrapper.USER_NAME, DataBaseWrapper.USER_EMAIL };
  private SQLiteDatabase database;
  public UserOperations(Context context) {
      dbHelper = new DataBaseWrapper(context);
  }
  public void open() throws SQLException {
      database = dbHelper.getWritableDatabase();
  }
  public void close() {
      dbHelper.close();
  }
  public User addUser(String name, String email) {
      ContentValues values = new ContentValues();
      values.put(DataBaseWrapper.USER_NAME, name);
      values.put(DataBaseWrapper.USER_EMAIL, email);
      long userId = database.insert(DataBaseWrapper.USERS, null, values);
      Cursor cursor = database.query(DataBaseWrapper.USERS,
              USER_TABLE_COLUMNS, DataBaseWrapper.USER_ID + " = "
                      + userId, nullnullnullnull);
      cursor.moveToFirst();
      User newComment = parseUser(cursor);
      cursor.close();
      return newComment;
  }
  public void deleteUser(User comment) {
      long id = comment.getId();
      System.out.println("Comment deleted with id: " + id);
      database.delete(DataBaseWrapper.USERS, DataBaseWrapper.USER_ID
              " = " + id, null);
  }
  public List getAllUsers() {
      List users = new ArrayList();
      Cursor cursor = database.query(DataBaseWrapper.USERS,
              USER_TABLE_COLUMNS, nullnullnullnullnull);
      cursor.moveToFirst();
      while (!cursor.isAfterLast()) {
          User user = parseUser(cursor);
          users.add(user);
          cursor.moveToNext();
      }
      cursor.close();
      return users;
  }
  public String getUserEmailById(long id) {
      User regUser = null;
      String sql = "SELECT " + DataBaseWrapper.USER_EMAIL + " FROM " + DataBaseWrapper.USERS
              " WHERE " + DataBaseWrapper.USER_ID + " = ?";
      Cursor cursor = database.rawQuery(sql, new String[] { id + "" });
      if (cursor.moveToNext()) {
          return cursor.getString(2);
      else {
          return "N/A";
      }
  }
  private User parseUser(Cursor cursor) {
     User user = new User();
     user.setId((cursor.getInt(0)));
     user.setName(cursor.getString(1));
     return user;
  }
}

Listing 12. CRUD code for our database calls

In our tests we’re going to mock out an addUser(name, email) call, see Listing 13.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
 * Unit tests for the User Database class.
 */
@SmallTest
public class DatabaseTest {
  private User joeSmith = new User("Joe""Smith");
  private final int USER_ID = 1;
  @Test
  public void testMockUser() {
      //mock SQLHelper
      SQLHelper dbHelper = Mockito.mock(SQLHelper.class);
      //have mockito return joeSmith when calling dbHelper getUser
      Mockito.when(dbHelper.getUser(USER_ID)).thenReturn(joeSmith);
      //Assert joeSmith is returned by getUser
      Assert.assertEquals(dbHelper.getUser(USER_ID), joeSmith);
  }
}

Listing: 13. testMockUser code

In the setup we mock out the dbHelper class as well as the underlying SQLiteDatabase. In testMockUser we do a simple test call that the returned user is Joe Smith.

Jenkins

To run the unit tests in Jenkins, click on Add Build Step->Invoke Gradle Script and add the testCompile task as shown below see figure 2.

Figure 2. Running unit tests in Jenkins.

Summary

In this post we’ve looked at a number of scenarios for using Mockito to isolate our tests from any underlying Android and Java dependencies. Working code can be found online at http://github.com/godfreynolan/AgileAndroid

Note a complete set of these testing blogs have been compiled together into an Agile Android mini-book, published by Apress and available from Amazon

This is Part 5 of 6, the remaining blogs can be found below.

Part 1 – Android Testing
Part 2 – Android Unit Testing
Part 3 – Hamcrest, JaCoCo, Mockito, and Jenkins for Android
Part 4 – Espresso Testing on Android
Part 5 – Android Mocking
Part 6 – Maintaining Someone Else’s Android Code

Related Posts