Unit testing API requests on Android
In this article I want to show you a tutorial on how I decided to test the API Request layer using RxAndroid, Retrofit, Mockito and the Model View Presenter (MVP) architecture. We will build an Android app using the free Star Wars API that displays characters data from the movie.
This tutorial requires a previous knowledge of Android development, Unit Tests and Reactive programming. If you want to see the entire code it's at this github repository.
Project Setup
On Android Studio (I’m using the version 2.2.3 currently) start a basic project with an empty activity. I will personally choose the minimum API of Android 4.0.3 (15) for this tutorial.
Following below are all the external dependencies needed at the build.gradle file and a brief description of each one:
compile 'io.reactivex:rxandroid:1.2.1'
compile 'com.squareup.retrofit2:retrofit:2.1.0'
compile 'com.squareup.retrofit2:converter-gson:2.1.0'
compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'testCompile 'junit:junit:4.12'
testCompile 'org.mockito:mockito-core:2.6.8'
- RxAndroid: library that brings Reactive Extensions to Android
- Retrofit: the HTTP Rest Client we will use to perform the API requests. I have chosen Retrofit because of its RxJava adapter that makes it easier to convert an HTTP response into an Observable.
- Gson: the JSON deserializer we will use to transform the HTTP response into java models.
- Junit: Java unit test framework. We will use some of its annotations and assertion methods.
- Mockito: besides its mocking feature, this framework will be used to verify interactions (i.e.: method calls) of classes.
Creating Model Classes
The next step will be the creation of our models that will represent the response returned from the characters request.
My suggestion is to use immutable models, given its advantages. For now, I will make all attributes as public and final instead of creating getter methods for each of them, because I intend to write a second part of this article where I explain how to validate models using reflection. Therefore, all values are set at object instantiation by its constructor.
CharactersModel.java:
public class CharacterModel {
public final String name;
public final String height;
public final String mass; // "SerializedName" is a Gson annotation to remap the original JSON field into another custom name@SerializedName("hair_color")
public final String hairColor;
@SerializedName("skin_color")
public final String skinColor;
@SerializedName("eye_color")
public final String eyeColor;
@SerializedName("birth_year")
public final String birthYear;
public final String gender;
public final String homeworld;
public final List<String> films;
public final List<String> species;
public final List<String> vehicles;
public final List<String> starships;
public final String created;
public final String edited;
public final String url;}
CharactersResponseModel.java:
public class CharactersResponseModel {
public final int count;
public final String next;
public final String previous;
public final List<CharacterModel> results;
}
API Request
Now that we have our project configured and our models created we will implement the API Request feature.
First, add the permission to access the internet in you manifest file:
<uses-permission android:name="android.permission.INTERNET"/>
As mentioned above, we will perform a request to obtain a list of Star Wars characters, then it will be required to add this endpoint description as described in Retrofit documentation in a form of a Java interface. As we’re going to see next, interface are easier to test since we can mock it using Mockito.
CharactersDataSource.java
public interface CharactersDataSource {
@GET("people/")
Observable<CharactersResponseModel> getCharacters();
}
Below it’s the implementation of the interface described above, basically it instantiates Retrofit with its RxJava Adapter, performs and HTTP request and converts the response into an Observable.
CharactersRemoteDataSource.java
public class CharactersRemoteDataSource implements CharactersDataSource {
private CharactersDataSource api;
private final String URL = "http://swapi.co/api/";
public CharactersRemoteDataSource() {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
this.api = retrofit.create(CharactersDataSource.class);
}
@Override
public Observable<CharactersResponseModel> getCharacters() {
return this.api.getCharacters();
}
}
Contract between Presenter and View Layer
Next we will create an interface defining the communication between the presenter and the view. The interface is necessary to maintain each class uncoupled and to make them easier to mock in the tests.
The view interface will have the following methods:
- onFetchDataStarted: notify the view that a request is about to start. Useful for giving user a feedback like displaying a progress bar.
- onFetchDataCompleted: notify the view that no more data will be returned.
- onFetchDataSuccess: return the requested data as its parameter, in the model type we have defined above.
- onFetchDataError: an error has occurred during the request. Could be a connection failure, for example.
The presenter interface will have the following methods:
- loadData: will tell the presenter to start fetching data.
- subscribe: notify the presenter that its view has become active. This can be used to trigger the API request.
- unsubscribe: notify the presenter that its view has become inactive. This can be used to cancel any previous request that hasn't returned yet.
- onDestroy: notify the presenter that its view instance is about to be destroyed. In this tutorial the view will instantiate the presenter and they will maintain a reference to each other by private properties, so we should clear this reference on the presenter when the view is going through the onDestroy lifecycle otherwise this will lead to memory leaks.
MainContract.java
public interface MainContract {
interface View {
void onFetchDataStarted();
void onFetchDataCompleted();
void onFetchDataSuccess(CharactersResponseModel charactersResponseModel);
void onFetchDataError(Throwable e);
}
interface Presenter {
void loadData();
void subscribe();
void unsubscribe();
void onDestroy();
}
}
Presentation Layer
Next we create our presenter implementing the interface defined above. This layer is the most complex layer in our application, so I will explain with more details.
In this tutorial, all the dependencies will be injected in the constructor and these will be defined by whoever instantiates the presenter. We could use a tool to handle the DI (like Dagger), but it's not the focus of this article. For now, we just need a simple dependency injection to help our unit tests.
Following there is a brief description of each dependency of our presenter:
- charactersDataSource: the Retrofit description of the Star Wars characters endpoint.
- backgroundScheduler: the Scheduler on which our API request Observable will operate.
- mainScheduler: the Scheduler on which we want our observer to wait for the API request Observable callbacks.
- view: any instance (can be a mock) that implements the view interface defined above.
In the constructor method we also have to initialize our CompositeSubscription instance, an object that will hold all Subscriptions generated by Observables. This object will be used to unsubscribe the Observers when the response is not needed anymore (ex.: app goes to the background state).
This is the initial implementation of the presenter, including its attributes and the constructor :
@NonNull
private CharactersDataSource charactersDataSource;
@NonNull
private Scheduler backgroundScheduler;
@NonNull
private Scheduler mainScheduler;
@NonNull
private CompositeSubscription subscriptions;
private MainContract.View view;public MainPresenter(
@NonNull CharactersDataSource charactersDataSource,
@NonNull Scheduler backgroundScheduler,
@NonNull Scheduler mainScheduler,
MainContract.View view) {
this.charactersDataSource = charactersDataSource;
this.backgroundScheduler = backgroundScheduler;
this.mainScheduler = mainScheduler;
this.view = view;
subscriptions = new CompositeSubscription();
}
Apart from loadData method of the presenter, the implementation of the others interface methods are simple and self-explanatory:
@Override
public void subscribe() {
loadData();
}
@Override
public void unsubscribe() {
subscriptions.clear();
}
@Override
public void onDestroy() {
this.view = null;
}
Finally, we implement the loadData method, that will use the CharactersDataSource instance to perform the API request and notify the view in case of success or error.
@Override
public void loadData() {
view.onFetchDataStarted();
subscriptions.clear();
Subscription subscription = charactersDataSource
.getCharacters()
.subscribeOn(backgroundScheduler)
.observeOn(mainScheduler)
.subscribe(new Observer<CharactersResponseModel>() {
@Override
public void onCompleted() {
view.onFetchDataCompleted();
}
@Override
public void onError(Throwable e) {
view.onFetchDataError();
}
@Override
public void onNext(CharactersResponseModel rootModel) {
view.onFetchDataSuccess(rootModel);
}
});
subscriptions.add(subscription);
}
Presenter tests
Even without the view implementation (the Activity in this case) we can already test the presenter. In this tutorial, we will focus only on two test cases:
- Given the presenter has requested data and its data source has returned the characters data successfully, I want to verify if the view receives it.
- Given the presenter has requested data and somehow the data source has returned error, I want to verify if the view receives the proper feedback.
In the default Android Studio project creation, there is already a package that includes a simple unit test class called ExampleUnitTest.java. This package is usually named by your applicationId followed by test. We will create in this package a class called MainPresenterTest.java.
At our test class, we will first declare our mock objects needed by the object under test (the presenter) as class attributes. The two dependencies needed to be mocked are the view and the data source.
@Mock
private CharactersDataSource charactersDataSource;
@Mock
private MainContract.View view;
The “@Mock” annotation is from the Mockito dependency we have declared at our build.gradle, and that means the library will be responsible to create a mock instance.
To make sure a new mock is created for each new test and therefore all tests are independent, we will initialize the mocks at the “@Before” step of this test class.
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
Every test case we will write will be represented by a public void method with the JUnit “@Test” annotation. You can run this test by clicking the green icon located next to the method name. The test below should pass since there is no implementation.
@Test
public void fetchValidDataShouldLoadIntoView() {}
We will now implement this test case starting by defining the behavior of the data source mock. Since we want the unit test to be fast and to not rely on internet connection, we will tell the data source to return a fixed response.
@Test
public void fetchValidDataShouldLoadIntoView() { CharactersResponseModel charactersResponseModel = new CharactersResponseModel(0, null, null, null); when(charactersDataSource.getCharacters())
.thenReturn(Observable.just(charactersResponseModel));
}
The code above means that whenever the method getCharacters() is called return the CharactersResponseModel instance declared.
Now we should instantiate the presenter passing the mocks as dependencies:
MainPresenter mainPresenter = new MainPresenter(
this.charactersDataSource,
Schedulers.immediate(),
Schedulers.immediate(),
this.view
);
One trick here is to “Schedulers.immediate()” as both background and main schedulers so there won’t be a delay when fetching the characters data.
Next we call the loadData method of our presenter interface which will allow us to write the test assertions. This is the current state of our test case:
public void fetchValidDataShouldLoadIntoView() {
CharactersResponseModel charactersResponseModel = new CharactersResponseModel(0, null, null, null);
when(charactersDataSource.getCharacters())
.thenReturn(Observable.just(charactersResponseModel));
MainPresenter mainPresenter = new MainPresenter(
this.charactersDataSource,
Schedulers.immediate(),
Schedulers.immediate(),
this.view
);
mainPresenter.loadData();
}
In brief, these are the view assertions that will be tested after loadData is called in this sequence:
- onFetchDataStarted method should be called once
- onFetchDataSuccess method should be called once and return the same exact model returned by CharactersDataSource.
- onFetchDataCompleted method should be called.
InOrder inOrder = Mockito.inOrder(view);inOrder.verify(view, times(1)).onFetchDataStarted();
inOrder.verify(view, times(1)).onFetchDataSuccess(charactersResponseModel);
inOrder.verify(view, times(1)).onFetchDataCompleted();
Now we write a similar test case, but to the error case:
@Test
public void fetchErrorShouldReturnErrorToView() {
Exception exception = new Exception();
when(charactersDataSource.getCharacters())
.thenReturn(Observable.<CharactersResponseModel>error(exception));
MainPresenter mainPresenter = new MainPresenter(
this.charactersDataSource,
Schedulers.immediate(),
Schedulers.immediate(),
this.view
);
mainPresenter.loadData();
InOrder inOrder = Mockito.inOrder(view);
inOrder.verify(view, times(1)).onFetchDataStarted();
inOrder.verify(view, times(1)).onFetchDataError(exception);
verify(view, never()).onFetchDataCompleted();
}
Conclusion
The MVP Architecture along with Mockito and Reactive Extensions make unit tests very simple to write. We have written just basic test cases for a one-screen application but the improvements in software quality and development speed will be more noticed when the application starts to grow.
Besides the advantage of automating the manual test, and therefore catching bugs earlier, I personally like the idea that unit tests give me confidence of the code I have written, allowing it to be refactored without introducing bugs.