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



我必须为调用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 调用?我仍然不清楚如何对功能进行单元测试。

这两个语句确实是您希望在单元测试中隔离的内容:

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缺少演员阵容,但你应该有整体的想法。

你是对的:既然你想测试你的单元(即呈现的代码),你应该模拟 API(主要是:vehicleApi实例)。

按原样,无法在您的代码中注入模拟的VehicleApi实例(嗯,有,但它会涉及使用反射......我们不要走这条路)。您可以应用控制反转来使代码可测试:不要在对象中构造VehicleApi,而是编写一个需要VehicleApi-instance 的构造函数:

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 作为模拟框架,但还有其他选项。

测试是用小黄瓜语编写的: -GIVEN块描述了前提条件,即被测单元和外部(模拟)系统所处的前提条件 -WHEN块执行应测试的操作 -THEN块验证受测设备是否处于预期状态。

相关内容

  • 没有找到相关文章

最新更新