How to create a retrofit.Response object during Unit Testing with Retrofit 2
Solution 1
The retrofit.Response
class has static factory methods to create instances:
public static <T> Response<T> success(T body) {
/* ... */
}
public static <T> Response<T> success(T body, com.squareup.okhttp.Response rawResponse) {
/* ... */
}
public static <T> Response<T> error(int code, ResponseBody body) {
/* ... */
}
public static <T> Response<T> error(ResponseBody body, com.squareup.okhttp.Response rawResponse) {
/* ... */
}
For example:
Account account = ...;
retrofit.Response<Account> aResponse = retrofit.Response.success(account);
Or:
retrofit.Response<Account> aResponse = retrofit.Response.error(
403,
ResponseBody.create(
MediaType.parse("application/json"),
"{\"key\":[\"somestuff\"]}"
)
);
Note: In latest Retrofit version (2.7.1) for Kotlin, it recommends to use extension method like this:
Response.error(
400,
"{\"key\":[\"somestuff\"]}"
.toResponseBody("application/json".toMediaTypeOrNull())
)
This falls under Effective Java Item 1: Consider static factory methods instead of constructors.
Solution 2
Heres how to mock just the retrofit responses
First you need to add these dependencies in build.gradle:
// mock websever for testing retrofit responses
testImplementation "com.squareup.okhttp3:mockwebserver:4.6.0"
testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0"
Mock a successful 200 response:
val mockResponseBody = Mockito.mock(MoviesResponse::class.java)
val mockResponse = Response.success(mockResponseBody)
Mock an unsuccessful response (eg 400, 401, 404):
val errorResponse =
"{\n" +
" \"type\": \"error\",\n" +
" \"message\": \"What you were looking for isn't here.\"\n"
+ "}"
val errorResponseBody = errorResponse.toResponseBody("application/json".toMediaTypeOrNull())
val mockResponse = Response.error<String>(400, errorResponseBody)
No need to create a mock webserver and all that extra work.
Solution 3
A Kotlin + Mockito + okhttp3 example using Response.Builder
val mockResponse: Response<MyResponseReturnType> =
Response.success(mock<MyResponseReturnType>(),
okhttp3.Response.Builder()
.code(200)
.message("Response.success()")
.protocol(Protocol.HTTP_1_1)
.request(Request.Builder().url("http://test-url/").build())
.receivedResponseAtMillis(1619053449513)
.sentRequestAtMillis(1619053443814)
.build())
Related videos on Youtube
Graham Smith
Developer, Creative and Entrepreneur are some of the titles applied to me. I have a passion for native mobile development, in particular Android, along with a strong background in SOA and distributed systems. I mentor new startups in the wider community. Previous Co-Organiser for NottsJS, a local meetup for JavaScript developers for 2+ years. 2010 Regional Contribution to Business Award Winner, awarded by the BCS “for the most promising year in industry student working in a computing field”. ► Native App Development ► Android (Android SDK) ► Kotlin, Java & C# ► Distributed Systems (APIs / REST) ► Cloud Technology (AWS) ► .NET Core ► Coaching / Mentoring / Line Management
Updated on July 08, 2022Comments
-
Graham Smith almost 2 years
While using RxJava and Retrofit 2 I am trying to create Unit Tests to cover when my app receives specific responses.
The issue I have is that with Retrofit 2 I cannot see a nice way of creating a retrofit.Response object without the use of reflection.
@Test public void testLogin_throwsLoginBadRequestExceptionWhen403Error() { Request.Builder requestBuilder = new Request.Builder(); requestBuilder.get(); requestBuilder.url("http://localhost"); Response.Builder responseBuilder = new Response.Builder(); responseBuilder.code(403); responseBuilder.protocol(Protocol.HTTP_1_1); responseBuilder.body(ResponseBody.create(MediaType.parse("application/json"), "{\"key\":[\"somestuff\"]}")); responseBuilder.request(requestBuilder.build()); retrofit.Response<LoginResponse> aResponse = null; try { Constructor<retrofit.Response> constructor= (Constructor<retrofit.Response>) retrofit.Response.class.getDeclaredConstructors()[0]; constructor.setAccessible(true); aResponse = constructor.newInstance(responseBuilder.build(), null, null); } catch (Exception ex) { //reflection error } doReturn(Observable.just(aResponse)).when(mockLoginAPI).login(anyObject()); TestSubscriber testSubscriber = new TestSubscriber(); loginAPIService.login(loginRequest).subscribe(testSubscriber); Throwable resultError = (Throwable) testSubscriber.getOnErrorEvents().get(0); assertTrue(resultError instanceof LoginBadRequestException); }
I have tried using the following but this creates an OkHttp Response vs a Retrofit Response.
Request.Builder requestBuilder = new Request.Builder(); requestBuilder.get(); requestBuilder.url("http://localhost"); Response.Builder responseBuilder = new Response.Builder(); responseBuilder.code(403); responseBuilder.protocol(Protocol.HTTP_1_1);
-
Graham Smith over 8 yearsThat's brilliant thank you! Just a couple of points, how do you get a typed Response object (thinking it might be good to illustrate for a more complete answer) and do you have a link to the last part you mentioned for a complete text/article? I will test tomorrow AM and update. Thanks again!
-
nhaarman over 8 yearsStoring the result directly in a typed variable (as I have done now) requires no extra difficulties. For some rare cases, you might need to give explicit type arguments like so:
Response.<Account>error(....)
. -
nhaarman over 8 yearsEffective Java is a book by Joshua Bloch, which is readily available.
-
Graham Smith over 8 yearsYes this works thanks. Only thing I cannot seem to get working is the line:
retrofit.Response<Account> aResponse = retrofit.Response.success(account);
I get a class cast exception (note the word success is spelt right in my version) -
nhaarman over 8 yearsExactly that does work over here. What is the message that comes with it?
-
Graham Smith over 8 years` MyObject thing = new MyObject(); retrofit.Response<MyObject> aResponse = retrofit.Response.success(thing);` returns at runtime
java.lang.ClassCastException: com.a.project.MyObject cannot be cast to retrofit.Response
-
Mr.G almost 7 years@nhaarman i cannot import Response . class . using rectrofit 2
-
Mahdi over 6 yearsI am trying to mock Retrofit respond not in test but in main code. but putting code 401 not lead to call authenticate methods