Skip to main content

Android : Sharing code and resources between UI and unit tests

As an Android developer you are already familiar with writing Unit and UI tests. This post is about "How to share the code and resources between your UI and unit tests?". I'd used "Appointments List" as an example scenario for this post.


Existing Setup

For UI testing, I'd have list of appointments API response in JSON format as "appointments.json" in res directory. The .json file is is converted to AppointmentsListResponse using JsonParser.

androidTest/java/com/example/AppointmentScreenTest.java
AppointmentsListResponse getFakeAppointmentsResponse() {
  InputStream in = getInstrumentation().getContext()
      .getResources()
      .openRawResource(com.example.android.internal.test.R.raw.appointments);

  AppointmentsListResponse fakeResponse =
      new JsonParser(in).parseTo(AppointmentsListResponse.class);  
}
androidTest/java/com/example/JsonParser.java

public class JsonParser
  String mStringToParse;
  private static Gson mGson = new Gson();

  public JsonParser(String parseString) {
    mStringToParse = parseString;
  }

  public JsonParser(InputStream is) {
    try (java.util.Scanner s = new java.util.Scanner(is)) {
      mStringToParse = s.useDelimiter("\\A").hasNext() ? s.next() : "";
    }
  }

  public <T> T parseTo(Class<T> targetClass) {
    try {
      return targetClass.cast(mGson.fromJson(mStringToParse, targetClass));
    } catch (ClassCastException e) {
      return null;
    }
  }
}
For presenter tests, I'd created fake response manually like shown below:

test/java/com/example/AppointmentPresenterTest.java
public static AppointmentsListResponse getFakeAppointmentListResponse() {
  AppointmentsListResponse response = new AppointmentsListResponse();
  response.setAppointments(getFakeAppointmentsList());
  response.setPatients(getFakePatientList());
  response.setDoctors(getFakeDoctorsList());
  return response;
}

What's the problem?

  • UI tests use real world data for testing but for unit tests we need to construct the data manually.
  • Requires 'Setter' methods for response class to facilitate test data creation.
  • Code repetition in both UI and unit tests.

What's the solution?


Step 1: Create a shared test folder called testShared  under app.
Step 2: Add a new File | New | Folder | Java Folder
Step 3: Add a new File | New | Folder | Java Resources Folder
Step 4: Add the test resources in the newly created resources directory. 
             e.g., For this scenario I have added "appointments.json" file. 
Step 5: Create a SharedTestHelper class which provides the test data for both UI & unit tests. 
Step 6: In your app/build.gradle, add the following lines
android.sourceSets {
  test {
    java.srcDirs += "$projectDir/src/testShared/java"
    resources.srcDirs += "$projectDir/src/testShared/resources"
  }

  androidTest {
    java.srcDirs += "$projectDir/src/testShared/java"
    resources.srcDirs += "$projectDir/src/testShared/resources"
  }
}
testShared/java/com/example/SharedTestHelper.java
public final class SharedTestHelper {
  private final Context mContext;
  private final ClassLoader mClassLoader;

  public SharedTestHelper() {
    this(null);
  }

  public SharedTestHelper(Context context) {
    mContext = context;
    if (context == null) {
      mClassLoader = ClassLoader.getSystemClassLoader();
    } else {
      mClassLoader = context.getClassLoader();
    }
  }

  public AppointmentsListResponse getFakeAppointmentsResponse() {
    InputStream in = mClassLoader.getResourceAsStream("appointments.json");
    AppointmentsListResponse fakeResponse =
        new JsonParser(in).parseTo(AppointmentsListResponse.class);
    return fakeResponse;
  }
}
Unit Tests - Use JVM's Built-In classloader. UI Tests - Use Dalvik's classloader which can be accessed via instrumentation context.
Usage

UI Tests

Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
new SharedTestHelper(instrumentation.getTargetContext()).getFakeAppointmentsResponse();

Unit Tests

new SharedTestHelper().getFakeAppointmentsResponse();
As you see we are reusing the same test data for both UI and unit tests, no extra setter methods. The entire mock data generation is shared between both tests, so there is no code repetition.
Thanks for @PavelDudka's awesome post Android Test tricks - Sharing code between UI & unit tests 

Comments