我有一个异步构建多个 DTO 的方法。它在一般用途中运行良好,因此我尝试为其编写一些单元测试。该方法如下所示:
public List<SurgeClientDto> clientLeaderboard(@RequestBody List<String> accountIds) throws ExecutionException, InterruptedException {
List<SurgeClientDto> surgeClients = new ArrayList<>(accountIds.size());
long start = System.currentTimeMillis();
List<CompletableFuture> futures = new ArrayList<>();
for (String accountId : accountIds) {
futures.add(
CompletableFuture.runAsync(() -> {
buildSurgeClientDto(surgeClients, accountId);
}, executor)
);
}
CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()])).get();
log.info("Time taken: {}ms", System.currentTimeMillis() - start);
return surgeClients;
}
我的测试如下:
@Test
@DirtiesContext
public void testGetLeaderboard() throws Exception {
// Given
final List<String> accounts = new ArrayList<>();
final String accountOne = "ABCDE";
final String accountTwo = "ZYXWV";
final String accountThree = "FAKE!";
final String clientForename = "John";
final String clientSurname = "Smith";
ClientDetailsCursorResult validOne = ClientDetailsCursorResult.builder()
.accountId(accountOne)
.forename(clientForename)
.surname(clientSurname)
.build();
ClientDetailsCursorResult validTwo = ClientDetailsCursorResult.builder()
.accountId(accountTwo)
.forename(clientForename)
.surname(clientSurname)
.build();
BalanceDetailsDto validBalanceDetailsDto = new BalanceDetailsDto();
validBalanceDetailsDto.setAvailableToWithdraw(100d);
validBalanceDetailsDto.setAvailableBalance(100d);
accounts.add(accountOne);
accounts.add(accountTwo);
accounts.add(accountThree);
// When
when(accountMaintenanceRestClient.getAccount(accountOne)).thenReturn(accountDTO());
when(accountMaintenanceRestClient.getAccount(accountTwo)).thenReturn(accountDTO());
when(accountMaintenanceRestClient.getAccount(accountThree)).thenReturn(null);
when(clientDetailsJdbc.getClientAccounts(accountOne)).thenReturn(Arrays.asList(validOne));
when(clientDetailsJdbc.getClientAccounts(accountTwo)).thenReturn(Arrays.asList(validTwo));
when(balanceDetailsService.getBalanceDetails(accountOne)).thenReturn(validBalanceDetailsDto);
when(balanceDetailsService.getBalanceDetails(accountTwo)).thenReturn(validBalanceDetailsDto);
List<SurgeClientDto> surgeClientDtos = surgeParisController.clientLeaderboard(accounts);
// Then
assertThat(surgeClientDtos.get(0).getAccountId(), is(accountOne));
assertThat(surgeClientDtos.get(0).getAvailableToTrade(), is(100d));
assertThat(surgeClientDtos.get(0).getAvailableToWithdraw(), is(100d));
assertThat(surgeClientDtos.get(0).getClientName(), is(clientForename + " " + clientSurname));
}
当我运行测试时,它陷入无限循环,没有输出。当我调试代码时,要执行的最后一行是
CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()])).get();
我在 buildSurgeClientDto()
中放置了一个断点,并尝试再次在 Debug模式下运行,但断点从未被触发。
这是为什么呢?我需要做一些特殊的事情来测试异步CompletableFutures
吗?
最佳答案
问题是你模拟了Executor
。它只是从不执行任务,所以测试只是挂起。你能做的就是使用简单的执行器并将其注入(inject)到你的 Controller 中:
private Executor executor = Executors.newSingleThreadExecutor();
而不是
@Mock
private Executor executor;
对于您的测试,它应该可以工作。您不需要测试 Executor
和 CompletableFuture
,因为它是 JDK 的一部分并且已经经过充分测试。
但是如果你需要模拟执行器,你应该模拟或 stub :
// CompletableFuture code:
executor.execute(new AsyncRun(dep, function));
否则测试将挂起。
关于java - CompletableFuture.allOf().get() 不在单元测试中运行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58101473/