我必须为调用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
块验证受测设备是否处于预期状态。