如何在使用Retrofit 2进行单元测试时创建一个retrofit.Response对象

66

在使用RxJava和Retrofit 2时,我试图创建单元测试以覆盖当我的应用程序接收特定响应时的情况。

问题在于,使用Retrofit 2时,我无法找到一种好的方式来创建retrofit.Response对象,而不使用反射。

@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);
}

我尝试使用以下代码,但它创建了一个OkHttp响应而不是Retrofit响应。

    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);
4个回答

114

retrofit.Response类有静态工厂方法来创建实例:

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) {
  /* ... */
}

例如:

Account account = ...;
retrofit.Response<Account> aResponse = retrofit.Response.success(account);

或者:

retrofit.Response<Account> aResponse = retrofit.Response.error(
  403, 
  ResponseBody.create(
    MediaType.parse("application/json"),
    "{\"key\":[\"somestuff\"]}"
  )
);

注意:在最新的Kotlin Retrofit版本(2.7.1)中,建议使用以下扩展方法:
Response.error(
  400,
  "{\"key\":[\"somestuff\"]}"
    .toResponseBody("application/json".toMediaTypeOrNull())
)

这属于《Effective Java》第1条:考虑使用静态工厂方法代替构造函数


3
太棒了,谢谢!只有几点需要注意,你如何获得一个已输入的Response对象(考虑到完整的回答可能更好),并且你是否有最后提到的部分的链接以获取完整的文章?我将在明天上午进行测试并更新。再次感谢! - Graham Smith
将结果直接存储在一个类型变量中(如我现在所做的那样)不需要额外的困难。对于某些罕见情况,您可能需要提供显式类型参数,例如:Response.<Account>error(....) - nhaarman
是的,这个可以工作,谢谢。唯一无法正常工作的是这行代码:retrofit.Response<Account> aResponse = retrofit.Response.success(account); 我得到了一个类转换异常(请注意,我的版本中单词“success”拼写正确)。 - Graham Smith
这确实在这里起作用。它带有什么消息? - nhaarman
在运行时返回java.lang.ClassCastException: com.a.project.MyObject无法转换为retrofit.Response - Graham Smith
显示剩余3条评论

7
这里是如何模拟 Retrofit 响应的方法:
首先,您需要在 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"

模拟成功的200响应:

val mockResponseBody = Mockito.mock(MoviesResponse::class.java)
val mockResponse = Response.success(mockResponseBody) 

模拟一个失败的响应(例如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)

不需要创建虚拟Web服务器和所有额外的工作。

5
一个使用Response.Builder的Kotlin + Mockito + okhttp3示例
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())

4

与其在字符串上使用toResponseBody()扩展函数,我像下面展示的那样在字节数组上使用了它。

Response.error(400, byteArrayOf().toResponseBody())

执行 byteArrayOf().toResponseBody() 会创建一个 ResponseBody。

这是一个空错误体的最简单答案。 - Minas Mina

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接