如何对Retrofit api调用进行单元测试



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

JUnit编译器从不执行 CallBack函数中的代码。

还有另一种选择,使所有api调用同步用于测试目的,但这并不适用于我的应用程序中的每种情况。

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

如果您使用.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();
        }
    }
}

我使用Mockito, robolelectric和Hamcrest库测试我的Retrofit回调。

首先,在模块的build.gradle中设置库堆栈:

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

在项目的全局构建中。Gradle添加以下行到buildscript依赖项:

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

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

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

假设你有一些改进的api调用来检索一个对象列表,这些对象需要放入某个适配器中用于RecyclerView等。我们想测试适配器是否在成功调用时填充了适当的项目。要做到这一点,我们需要切换您的Retrofit接口实现,您使用它来使用mock进行调用,并利用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));
    }
}

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

就是这样。我强烈建议使用Robolectric(带有Robolectric gradle插件)和Mockito,这些库使测试android应用程序变得更加容易。我从下面的博客文章中学到了这个方法。还有,参考这个答案

Update:如果您正在使用RxJava的Retrofit,请查看我的其他答案

  • JUnit框架永远不会执行CallBack函数中的代码,因为执行的主线程在检索响应之前终止。您可以如下所示使用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();
        } 
    }
    
  • 这个测试样本可能也有帮助。

  • 我的建议是不要在android应用程序中执行API响应测试。有许多外部工具可以做到这一点。

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

如果已经封装retrofit2.0与rx与restful

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)

正如@Islam Salah所说:

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

你可以用awaitility来解决这个问题。在StackOverflow上查看这个答案

最新更新