【问题标题】:How to unit test Retrofit api calls?如何对 Retrofit api 调用进行单元测试?
【发布时间】:2015-11-29 05:19:59
【问题描述】:

我正在尝试为每个可能的代码块集成单元测试用例。 但是我在为 通过改造进行的 api 调用添加测试用例时遇到了问题。

JUnit 编译器从不执行回调函数中的代码

还有另一个选项可以使所有 api 调用 同步 以用于测试目的,但对于我的应用程序中的所有情况而言,这并不是可能的。

我该如何解决这个问题?我必须通过任何方式在 api 调用中添加测试用例。

【问题讨论】:

  • 您能否先分享一个示例,说明您最初是如何为 api 调用编写测试用例的?

标签: android unit-testing junit android-gradle-plugin retrofit


【解决方案1】:

我使用 Mockito、Robolectric 和 Hamcrest 库测试了我的 Retrofit 回调。

首先,在你的模块的 build.gradle 中设置 lib 栈:

dependencies {
    testCompile 'org.robolectric:robolectric:3.0'
    testCompile "org.mockito:mockito-core:1.10.19"
    androidTestCompile 'org.hamcrest:hamcrest-library:1.1'
}

在 jour 项目的全局 build.gradle 中添加以下行到 buildscript 依赖项:

classpath 'org.robolectric:robolectric-gradle-plugin:1.0.1'

然后在 Android Studio 中进入“Build Variants”菜单(要快速找到它,按 Ctrl+Shift+A 并搜索它),并将“Test Artifact”选项切换为“Unit Tests”。 Android Studio 会将您的测试文件夹切换到“com.your.package (test)”(而不是 androidTest)。

好的。设置完成,是时候写一些测试了!

假设您有一些改造 api 调用来检索需要放入 RecyclerView 等适配器的对象列表。我们想测试适配器是否在成功调用时填充了正确的项目。 为此,我们需要切换您的 Retrofit 接口实现,您可以使用该接口通过模拟进行调用,并利用 Mockito ArgumentCaptor 类进行一些虚假响应。

@Config(constants = BuildConfig.class, sdk = 21,
    manifest = "app/src/main/AndroidManifest.xml")
@RunWith(RobolectricGradleTestRunner.class)
public class RetrofitCallTest {

    private MainActivity mainActivity;

    @Mock
    private RetrofitApi mockRetrofitApiImpl;

    @Captor
    private ArgumentCaptor<Callback<List<YourObject>>> callbackArgumentCaptor;

    @Before
    public void setUp() {            
        MockitoAnnotations.initMocks(this);

        ActivityController<MainActivity> controller = Robolectric.buildActivity(MainActivity.class);
        mainActivity = controller.get();

        // Then we need to swap the retrofit api impl. with a mock one
        // I usually store my Retrofit api impl as a static singleton in class RestClient, hence:
        RestClient.setApi(mockRetrofitApiImpl);

        controller.create();
    }

    @Test
    public void shouldFillAdapter() throws Exception {
        Mockito.verify(mockRetrofitApiImpl)
            .getYourObject(callbackArgumentCaptor.capture());

        int objectsQuantity = 10;
        List<YourObject> list = new ArrayList<YourObject>();
        for(int i = 0; i < objectsQuantity; ++i) {
            list.add(new YourObject());
        }

        callbackArgumentCaptor.getValue().success(list, null);

        YourAdapter yourAdapter = mainActivity.getAdapter(); // Obtain adapter
        // Simple test check if adapter has as many items as put into response
        assertThat(yourAdapter.getItemCount(), equalTo(objectsQuantity));
    }
}

通过右键单击测试类并点击运行来继续测试。

就是这样。我强烈建议使用 Robolectric(带有 robolectric gradle 插件)和 Mockito,这些库使测试 android 应用程序变得更加容易。 我从下面的blog post 学到了这个方法。另请参阅this answer

更新:如果您正在使用 Retrofit 和 RxJava,请查看 my other answer on that

【讨论】:

  • 我阅读了同一篇博文,发现其中的解释很有帮助。对我来说,一个难点是这个例子。我尝试阅读示例,然后在我的项目中以类似的方式实施测试。我了解到,仅从测试代码 sn-p 并不清楚的是,在博客文章中描述的测试中,有一个 Activity 在 onCreate() 方法中进行网络调用。需要进行此调用,以便在测试中可以运行对 Mockito.verify() 的调用。如果您不在应用程序代码中进行实际的“网络”调用,测试代码将不会按原样运行。
  • 这不是问题的答案
  • @JemshitIskenderov 怎么来的?愿意指出它的问题吗?
  • 1) 问题是 jUnit,你介绍了 Roboelectric,hamcrest 2) 这是关于异步回调没有被执行,你从来没有回答为什么会发生,问题是什么...... 3) 你刚刚展示你在你的项目中做了什么,所以它不是这个问题的答案
  • @JemshitIskenderov 你一定错过了“任何关于另一种方法的建议也会有所帮助。”问题的一部分。这就是这个答案 - 一种建议的工作方法。
【解决方案2】:

如果您使用 .execute() 而不是 .enqueue() 它会使执行同步,因此测试可以正常运行而无需导入 3 个不同的库和添加任何代码或修改构建变体。

喜欢:

public class LoginAPITest {

    @Test
    public void login_Success() {

        APIEndpoints apiEndpoints = RetrofitHelper.getTesterInstance().create(APIEndpoints.class);

        Call<AuthResponse> call = apiEndpoints.postLogin();

        try {
            //Magic is here at .execute() instead of .enqueue()
            Response<AuthResponse> response = call.execute();
            AuthResponse authResponse = response.body();

            assertTrue(response.isSuccessful() && authResponse.getBearer().startsWith("TestBearer"));

        } catch (IOException e) {
            e.printStackTrace();
        }

    }

}

【讨论】:

  • 虽然这是一种有效的方法,但问题指出 还有另一种选择可以使所有 api 调用同步以进行测试,但对于我的应用程序中的所有情况而言,这并非不可能。
  • mybad mybad mybad
  • 这个解决方案给了我一个非常有用的线索。谢谢
  • 什么是 RetrofitHelper?是自定义类还是依赖类
  • 还想看RetrofitHelper
【解决方案3】:
  • JUnit 框架从不执行回调函数中的代码,因为执行的主线程在检索到响应之前终止。可以使用CountDownLatch,如下图:

    @Test
    public void testApiResponse() {
        CountDownLatch latch = new CountDownLatch(1);
        mApiHelper.loadDataFromBackend(new Callback() {
            @Override
            public void onResponse(Call call, Response response) {
                System.out.println("Success");
                latch.countDown();
            }
    
            @Override
            public void onFailure(Call call, Throwable t) {
                System.out.println("Failure");
                latch.countDown();
            }
        });
    
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } 
    }
    
  • 这个test sample 也可能有帮助。

  • 我的建议是不要在 android 应用程序中测试 API 响应。为此有很多外部tools

【讨论】:

    【解决方案4】:

    Junit 不会等待异步任务完成。您可以使用 CountDownLatch(不需要外部库的优雅解决方案)来阻塞线程,直到您收到来自服务器的响应或超时。

    您可以使用CountDownLatch。 由于调用了 countDown() 方法,await 方法一直阻塞,直到当前计数达到零,之后所有等待的线程都被释放,任何后续的 await 调用立即返回。

    //Step 1: Do your background job 
     latch.countDown(); //Step 2 : On completion ; notify the count down latch that your async task is done
     
     latch.await(); // Step 3: keep waiting
    

    或者您可以在 await 调用中指定超时

      try {
          latch.await(2000, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    

    示例测试用例

    void testBackgroundJob() {
    
            Latch latch = new CountDownLatch(1);
    
            //Do your async job
            Service.doSomething(new Callback() {
    
                @Override
                public void onResponse(){
                    ACTUAL_RESULT = SUCCESS;
                    latch.countDown(); // notify the count down latch
                    // assertEquals(..
                }
    
            });
    
            //Wait for api response async
            try {
                latch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            assertEquals(expectedResult, ACTUAL_RESULT);
    
        }
    

    【讨论】:

      【解决方案5】:

      如果已经用 rx 和 restful 封装了 retrofit2.0

      open class BaseEntity<E> : Serializable {
          /*result code*/
          var status: Int = 0
          /**data */
          var content: E? = null
      }
      

      和服务器 api 请求类似

      @GET(api/url)
      fun getData():Observable<BaseEntity<Bean>>
      

      你的服务只回调一个同步请求 Observable

      val it = service.getData().blockingSingle()
      assertTrue(it.status == SUCCESS_CODE)
      

      【讨论】:

        【解决方案6】:

        作为@Islam Salah said:

        JUnit 框架从不执行 CallBack 函数中的代码,因为执行的主线程在检索到响应之前终止。

        您可以使用awaitility 解决问题。查看 StackOverflow 上的 this 答案。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-08-17
          • 1970-01-01
          • 2012-01-08
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多