How to test Android Apps: Other details

A lot can be said about testing. But to finish this testing guide, here are some subjects you will run into when testing Android apps.

Mock components (+ global testing flag)

We've seen how to mock dependencies in unit testing, to test only one thing at a time. But when testing for integration (like in instrumentation tests), you need to mock specific components of your app, globally.

You can work with your dependency injection library to switch components for their mocked versions (an example). Or use custom flavors in Android Studio to add them.

A simpler way, is to have a global testing flag, to check in places where you want a mock on testing. Android doesn't have such a flag, so here's an hack on how to get it:

public class AndroidApplication extends Application {

    private boolean testing;

    @Override public void onCreate() {
        super.onCreate();
        checkTestMode();
    }

    public boolean isTesting() {
        return this.testing;
    }

    // Test loading a random test class, to check if we're in test mode
    private void checkTestMode() {
        try {
            getClassLoader().loadClass("com.example.app.MainActivityTest");
            this.testing = true;
        } catch (final Exception e) {
            this.testing = false;
        }
    }
}

Note: make sure you pick a test class that you will always have in your project.

Now you can check application.isTesting() where you want a different behaviour when testing.

Mocking Servers

If your app integrates with a server API, you don't want your tests to hit the server every time they run. They would be slower and could fail for reasons you don't control.

While in unit tests it's easy to mock API calls, on instrumentation tests it's harder. Ideally, you want to fake as little as possible. This means overriding just the HTTP calls and returning what the server would (a json response, for example).

An example of a fake client for Retrofit (version 1):

import retrofit.client.Client;
import retrofit.client.Request;
import retrofit.client.Response;
import retrofit.mime.TypedByteArray;

public class MockClient implements Client {

    @Override
    public Response execute(Request request) throws IOException {
        String requestPath = Uri.parse(request.getUrl()).getPath();

        String responseString = "";
        if (requestPath.matches("/people")) {
            responseString = PEOPLE_RESPONSE;
        } else if (requestPath.matches("/login")) {
            responseString = LOGIN_RESPONSE;
        }

        return new Response(request.getUrl(), 200, "nothing", Collections.EMPTY_LIST,
                new TypedByteArray("application/json", responseString.getBytes()));
    }

    private static final String PEOPLE_RESPONSE = "{people:{}}";
    private static final String LOGIN_RESPONSE = "{session:{token:\"token\", profile:{id:\"profile\"}}}";
}

As you can see, based on the URL you are requesting, it returns a specific json response. You can also check for the HTTP method, to return different responses for GET or POST requests.

You need to set the fake Retrofit client when testing:

Client client;

if (app.isTesting()) {
    client = new MockClient();
} else {
    client = new OkClient(new OkHttpClient());
}

RestAdapter restAdapter = new RestAdapter.Builder()
        .setClient(client)
        .setEndpoint(endpoint)
        .build();

Testing data

When writing tests, you need to create fake objects (database models, for example) to send around. Since you need to fill that objects with fake data, we ported Faker to Java, so you can have realistic fake data.

If you find yourself creating fake objects too often, you probably want to write some factory classes to re-use that code.

In Ruby on Rails, you have libraries like Factory Girl, but I haven't found one for Java/Android. You need to write yours from scratch. Something like:

public class PersonFactory {

    private final Faker faker;
    private final Database database;

    public CardFactory(Database database) {
        this.faker = new Faker();
        this.database = database;
    }

    public Card build() {
        Person person = new Person();
        person.setName(faker.name.name());
        person.setEmail(faker.internet.email());
        person.setCreatedAt(faker.time.backward(365));
        return person;
    }

    public Card create() {
        return create(build());
    }

    private Card create(Person person) {
        database.createPerson(person);
        return person;
    }
}

Code coverage

It's a good practice to keep an eye on the code coverage your tests have. You can find blind spots of untested code in your apps, or routinely check if the team is not slacking in their tests.

Android Studio already comes with Code Coverage out of the box, although only for one Build Environment at a time. It uses the Jacoco library under the hood.

You just have to enable it on your gradle file:

buildTypes {
    debug {
        ...
        testCoverageEnabled = true
    }
}

After you run your tests, the reports will be generated in the /build/outputs/reports/coverage/debug/folder.


How to test Android Apps

  1. Introduction
  2. Architecture
  3. Unit Testing
  4. Instrumentation Testing
  5. Other details
TechSérgiofaker, testing, retrofit