我必须为调用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调用吗?我仍然不清楚如何对该功能进行单元测试。
答案 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
块验证被测设备是否处于预期状态。