如何为调用API的方法编写JUnit测试?

时间:2019-06-18 19:38:50

标签: java junit mockito

我必须为调用API的类编写测试,然后处理响应。该类具有两个公共职能和一个私人职能。第一个公共方法获取ID列表。在每个ID的循环中调用第二个公共方法,以获取与ID关联的详细信息。在第二个公共方法内部调用了私有方法,因为基于id的获取详细信息的调用是异步进行的。

我是JUnits的新手,虽然我知道我不应该测试API调用,而只是测试我的函数,但我仍然不知道单元测试应该断言什么。

下面是我的功能:

public List<Integer> fetchVehicleIds(String datasetId) throws ApiException {

    VehiclesApi vehiclesApi = new VehiclesApi();

    List<Integer> vehicleIds;
    vehicleIds = vehiclesApi.vehiclesGetIds(datasetId).getVehicleIds();

    return vehicleIds;
}

 public List<VehicleResponse> fetchVehicleDetails(String datasetId, List<Integer> vehicleIds) throws InterruptedException, ApiException {

    CountDownLatch latch = new CountDownLatch(vehicleIds.size());
    List<VehicleResponse> vehiclesList = new ArrayList<>();

    for (Integer vehicleId: vehicleIds) {
        populateEachVehicleDetail(datasetId, vehicleId, vehiclesList, latch);
    }

    latch.await();

    return vehiclesList;
}

private void populateEachVehicleDetail(String datasetId, Integer vehicleId, List<VehicleResponse> vehiclesList, CountDownLatch latch) throws ApiException {

    ApiCallback<VehicleResponse> vehicleResponseApiCallback = new ApiCallback<VehicleResponse>() {
        @Override
        synchronized public void onSuccess(VehicleResponse result, int statusCode, Map<String, List<String>> responseHeaders) {
            vehiclesList.add(result);
            latch.countDown();
        }
    };

    VehiclesApi vehiclesApi = new VehiclesApi();
    vehiclesApi.vehiclesGetVehicleAsync(datasetId,vehicleId,vehicleResponseApiCallback);

}

根据我到目前为止所做的研究,我认为我必须使用Mockito模拟API调用吗?我仍然不清楚如何对该功能进行单元测试。

2 个答案:

答案 0 :(得分:4)

这两个语句确实是您要在单元测试中隔离的东西:

private void populateEachVehicleDetail(String datasetId, Integer vehicleId, List<VehicleResponse> vehiclesList, CountDownLatch latch) throws ApiException {
....
    VehiclesApi vehiclesApi = new VehiclesApi();
    vehiclesApi.vehiclesGetVehicleAsync(datasetId,vehicleId,vehicleResponseApiCallback);
...
}

1)使您的依赖关系可模拟

但是您只能模拟可以从类的客户端设置的内容。
这里的API是一个局部变量。因此,您应该更改类以公开依赖关系,例如在构造函数中。
这样,您可以轻松地对其进行嘲笑。

2)使您的模拟不返回结果,而是调用回调。

在同步调用上下文中,您要模拟返回的结果。
在带有回调的异步调用上下文中,情况有所不同。实际上,回调不会返回到调用者,而是会通过调用回调来提供调用结果。因此,您想要的是,模拟API会使用模拟参数(代表您单位数据集的参数)调用onSuccess()回调测试:

@Override
synchronized public void onSuccess(VehicleResponse result, int statusCode, Map<String, List<String>> responseHeaders) {
    vehiclesList.add(result);
    latch.countDown();
}

在单元测试中,应该以这种方式模拟每个预期调用的回调:

@Mock
VehiclesApi vehiclesApiMock;
// ...

// when the api method is invoked with the expected dataSetId and vehicleId
Mockito.when(vehiclesApiMock.vehiclesGetVehicleAsync(Mockito.eq(datasetId), Mockito.eq(vehicleId),
                                                 Mockito.any(ApiCallback.class)))
       // I want to invoke the callback with the mocked data
       .then(invocationOnMock -> {
           ApiCallback<VehicleResponse> callback = invocationOnMock.getArgument(2);
           callback.onSuccess(mockedVehicleResponse, mockedStatusCode,
                              mockedResponseHeaders);
           return null; // it is a void method. So no value to return in T then(...).
       });

我认为ApiCallback缺少演员表,但您应该有一个总体思路。

答案 1 :(得分:3)

您是对的:由于您要测试单元(即显示的代码),因此应该模拟API(主要是vehicleApi实例)。

目前,您无法在代码中注入VehicleApi的模拟实例(可以,但是会涉及到反射的使用……让我们不要走这条路) 。使代码可测试的方法是使用Inversion of Control:而不是在对象中构造VehicleApi,而是编写一个期望VehicleApi实例的构造函数:

public class YourClass {
    private final VehicleApi vehicleApi;

    public YourClass(final VehicleApi vehicleApi) {
        this.vehicleApi = vehicleApi;
    }

    [...]
}

您赢了什么?好了,现在您可以将模拟对象注入到要测试的单元中了:

@RunWith(MockitoJRunner.class)
public class YourClassTest {

    private final VehicleApi vehicleApiMock = mock(VehicleApi.class);
    private final YourClass underTest = new YourClass(vehicleApiMock);

    @Test
    void someTest() {
        // GIVEN
        [wire up your mock if necessary]

        // WHEN
        [write the test-call]

        // THEN
        [verify that the unit under test is in the expected state]
    }
}

本示例假定JUnit5为测试框架,而Mockito为模拟框架,但是还有其他选择。

测试用Gherkin language编写: -GIVEN块描述了先决条件,即被测单元和外部(模拟)系统都在其中 -WHEN块执行应测试的动作 -THEN块验证被测设备是否处于预期状态。