【问题标题】:How to create a retrofit.Response object during Unit Testing with Retrofit 2如何在使用 Retrofit 2 进行单元测试期间创建一个 retrofit.Response 对象
【发布时间】:2016-01-14 04:20:18
【问题描述】:

在使用 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 响应与改造响应。

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

【问题讨论】:

    标签: android unit-testing mockito retrofit rx-java


    【解决方案1】:

    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())
    )
    

    这属于有效的 Java 项目 1:考虑静态工厂方法而不是构造函数

    【讨论】:

    • 太好了,谢谢!只是几点,你如何获得一个类型化的 Response 对象(认为它可能会很好地说明一个更完整的答案)并且你是否有一个链接到你提到的完整文本/文章的最后一部分?我明天上午测试并更新。再次感谢!
    • 将结果直接存储在类型变量中(就像我现在所做的那样)不需要额外的困难。在极少数情况下,您可能需要像这样给出明确的类型参数:Response.&lt;Account&gt;error(....)
    • Effective Java 是 Joshua Bloch 的一本书,很容易获得。
    • 是的,谢谢。唯一我似乎无法正常工作的是:retrofit.Response&lt;Account&gt; aResponse = retrofit.Response.success(account);我得到一个类转换异常(请注意我的版本中成功拼写正确)
    • 正是这里的工作。随之而来的信息是什么?
    【解决方案2】:

    这里是如何模拟改造响应

    首先需要在 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)
    

    无需创建模拟网络服务器和所有额外工作。

    【讨论】:

      【解决方案3】:

      使用 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。

        【讨论】:

          猜你喜欢
          • 2015-11-29
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-07-17
          • 1970-01-01
          相关资源
          最近更新 更多