Android restclient repository testing recipe
And again… another Android testing recipe is here!!!
More specifically, the third one (you can also check recipe #1, recipe #2, and the rails-api-base project where all of them come from).
Today we are going to cook a test for data repository and restful client, the piece of code in charge of making the api server calls.
Basic Ingredients
Method
1. Get a rest client
The basic ingredient of this recipe is a rest client to be tested. This particular case is composed by a Repository and a RestApi. The Repository is called when data is required and it calls the RestApi client to retrieve the data from the server. I’ve implemented my RestApi using Retrofit with its RxJava adapter.
Let’s see an example of a single call through these elements.
Suppose we have a Notes
model api with authentication.
A note
is composed by a title
and a content
and
we need to authenticate ourself when managing notes.
In this particular case we want to create a note:
INPUT:
POST /notes
Header['Authentication'] = params['auth_token']
{
"note" : {
"title" : params["note"]["title"],
"content" : params["note"]["content"]
}
}
_________________________________________________
OUTPUT:
status: 201 created
body:
{
"id" : int,
"title" : "String",
"content" : "String",
"user_id" : int
}
So the RestApi.java
should look like this:
public interface RestApi {
@POST("/notes")
Observable<Response<NoteEntity>> createNote(
@Header("Authorization") String token, @Body NoteEntity note);
}
And the NoteDataRepository.java
:
@Singleton
public class NoteDataRepository extends RestApiRepository implements NoteRepository {
private final RestApi restApi;
@Inject
public NoteDataRepository(RestApi restApi) {
this.restApi = restApi;
}
@Override
public Observable<NoteEntity> createNote(UserEntity user, final NoteEntity note) {
return this.restApi.createNote(user.getAuthToken(), note)
.map(new Func1<Response<NoteEntity>, NoteEntity>() {
@Override
public NoteEntity call(Response<NoteEntity> noteEntityResponse) {
handleResponseError(noteEntityResponse);
return noteEntityResponse.body();
}
});
}
}
Note: In this case I use Dagger 2 to inject
the RestApi
, but that’s not essential.
createNote
method just calls the restApi
to create the note with its own
params and then maps the response event to unwrap the response.
RestApiRepository
just implements the method handleResponseError
that checks if the Response
is successful or throws an error otherwise.
(This is not necessary for this recipe, but you can check its source code here ).
You don’t need to have exactly the same code or pieces as here, this is only an example. Just be sure to have a Retrofit client using RxJava adapter.
2. Setup your gradle
The build.gradle
should look like this:
...
dependencies {
...
compile "io.reactivex:rxjava:1.1.1"
compile "com.squareup.retrofit2:retrofit:2.0.0-beta4"
compile "com.squareup.retrofit2:converter-gson:2.0.0-beta4"
compile "com.squareup.retrofit2:adapter-rxjava:2.0.0-beta4"
testCompile "junit:junit:4.10"
testCompile "com.squareup.okhttp3:mockwebserver:3.2.0"
}
...
MockWebServer is the secret ingredient we will use to test them all…
3. Test it
First of all, we must setup the test: NoteDataRepositoryTest.java
(without tests yet):
public class NoteDataRepositoryTest {
private MockWebServer mockWebServer;
private Gson gson;
private NoteDataRepository noteDataRepository;
private TestSubscriber testSubscriber;
private UserEntity fakeUser;
private NoteEntity fakeNote;
@Before
public void setUp() throws IOException {
this.mockWebServer = new MockWebServer();
this.mockWebServer.start();
this.gson = new GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
this.noteDataRepository = new NoteDataRepository(
new Retrofit.Builder()
.baseUrl(mockWebServer.url("/"))
.addConverterFactory(GsonConverterFactory.create(this.gson))
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build()
.create(RestApi.class)
);
this.testSubscriber = new TestSubscriber();
this.fakeUser = new UserEntity("some@mail");
this.fakeUser.setAuthToken("AUTH_TOKEN");
this.fakeNote = new NoteEntity("TITLE", "Content");
}
@After
public void tearDown() throws Exception {
this.mockWebServer.shutdown();
}
...
Add them step by step:
- MockWebServer: To mock the responses and check the requests.
- Gson: To tell retrofit how to match the response json params with java object attributes. I use camelCase(client) to lowe_case_with_underscores(server). (you can skip this step)
- RestApi: We create the retrofit class with our RestApi interface, Gson converter, RxJava adapter and the url of the mockWebServer.
- DataRepository: Initialized with the previous RestApi, is our test target.
- TestSubscriber: RxJava test element to check the observable events.
- UserEntity: Used to send the token authentication param.
- NoteEntity: Used to mock the note params.
Don’t forget to shut down the server after using it.
Everything is ready so… it’s testing time!
...
@Test
public void testCreateNoteRequest() throws Exception {
this.mockWebServer.enqueue(new MockResponse());
this.noteDataRepository.createNote(this.fakeUser, this.fakeNote)
.subscribe(this.testSubscriber);
RecordedRequest request = this.mockWebServer.takeRequest();
assertEquals("/notes", request.getPath());
assertEquals("POST", request.getMethod());
assertEquals("AUTH_TOKEN", request.getHeader("Authorization"));
assertEquals(this.gson.toJson(this.fakeNote).toString(),
request.getBody().readUtf8());
}
...
This first test checks the output params, namely the request.
Realize that it just enqueues a simple new MockResponse()
because we don’t care here about it (but we still need it).
After making the call to the repository, we get the request
.
From that request
we can check the path, method, headers, body…
The last sentence is the trickiest one,
but it just compares the json params sent with the
fakeNote
object (we have set as param) json serialized.
And what about the response?
...
private static final String RESPONSE_BODY =
'{"id":1,"title":"Title","content":"Content...","user_id":2}';
@Test
public void testCreateNoteSuccessResponse() throws Exception {
this.mockWebServer.enqueue(
new MockResponse().setResponseCode(201).setBody(RESPONSE_BODY));
this.noteDataRepository.createNote(this.fakeUser, this.fakeNote)
.subscribe(this.testSubscriber);
this.testSubscriber.awaitTerminalEvent();
NoteEntity responseNote = (NoteEntity) this.testSubscriber.getOnNextEvents().get(0);
assertEquals(responseNote.getId(), 1);
assertEquals(responseNote.getTitle(), "Title");
assertEquals(responseNote.getContent(), "Content...");
assertEquals(responseNote.getUserId(), 2);
}
...
Here we add a response code and body to the MockResponse.
Then, we must subscribe the testSubscriber
and make it wait.
testSubscriber
gives us the response from the createNote
call.
Realize that here we have double test:
- First: the json decode to our
NoteEntity
of the server response. - Second: the map we apply inside the DataRepository
to the
Response<MessageEntity>
to convert it toMessageEntity
.
Conclusions
Although this is a simple example, there are infinite possibilities and you can adapt it easily to your needs. Give this recipe a try and enjoy testing your Android client application!
Comments and suggestions are welcome!