使用 Mockito 测试 CompletableFuture.supplyAsync

问题描述 投票:0回答:2

我正在尝试使用mockito测试CompletableFuture.supplyAsync函数,但测试未完成可能是因为可完成的未来没有返回。我不确定代码中缺少什么。有谁可以帮忙吗?

我写的代码如下。 因此有返回 User 的 UserService 类、返回用户实体的 UserEntityService 类和一个检查实体是否属于用户的验证类。

我想测试传递的实体是否属于用户。

class UserService {

    CompletableFuture<User> getUser(String userName) {
        log.info("Fetching User with username {}", userName);
        return CompletableFuture.supplyAsync(
                () -> getUserByPortalUserName(userName));
    }
}
class UserEntityService {

    CompletableFuture<List<UserEntity>> getUserEntities(Long userId) {
        log.info("Retrieving all entities for user id {}", userId);
        return CompletableFuture.supplyAsync(
                () -> getAllByUserId(userId));
    }
}
class UserValidationService {

    public boolean validateUserCounterparty(UserRequest request) 
                   throws ExecutionException, InterruptedException {
        CompletableFuture<Boolean> result = userService.getUser(request.getUserName())
                .thenCompose(user -> userEntityService.getUserEntities(user.getUserId()))
                .thenCompose(userEntities -> validate(userEntities, request.getUserEntities()));

        Boolean validationStatus = result.get();
        if (!validationStatus) {
            log.error("Validation failed for user name {}", request.getUserName());
        }
        return validationStatus;
    }
}

测试用例写为

@ExtendWith(MockitoExtension.class)
class UserValidationServiceTest {

    @Mock
    UserService userService;

    @Mock
    UserEntityService userEntityService;

    @InjectMocks
    UserValidationService userValidationService;

    @Before
    public void init() {
        MockitoAnnotations.openMocks(this);
    }

    @Test
    public void validateUser() throws ExecutionException, InterruptedException {
        CompletableFuture<User> userFuture = new CompletableFuture<>();
        CompletableFuture<List<UserEntity>> userEntityFuture = new CompletableFuture<>();

        Mockito.doReturn(userFuture).when(userService).getUser(anyString());
        Mockito.doReturn(userEntityFuture).when(userEntityService).getUserEntities(anyLong());

        UserRequest request = UserRequest.builder()
                .userName("admin")
                .userEntities(List.of("US", "ASIA", "EUROPE")).build();


        boolean result = validationService.validateUserCounterparty(request);
        assertTrue(result);

    }
}

执行此测试时,它会进入无限循环并且永远不会停止。我想这是因为完整的未来不会回来,但我对如何预防它没有足够的知识。

我应该做什么修改来防止它?

java spring-boot junit mockito completable-future
2个回答
3
投票

在您的测试方法中,您使用

CompletableFuture
创建
new
实例。 JavaDoc 指出

公共 CompletableFuture()

创建一个新的不完整的 CompletableFuture。

因此您创建的对象永远不会完成,这就是测试无限运行的原因。它实际上不是一个循环,而是等待一个阻塞操作完成,但这种情况永远不会发生。

您需要做的是定义一个立即完成或在一段时间后完成的

CompletableFuture
。最简单的方法是使用静态的completedFuture()方法:

CompletableFuture<User> userFuture =
        CompletableFuture.completedFuture(new User());
CompletableFuture<List<UserEntity>> userEntityFuture =
        CompletableFuture.completedFuture(List.of(new UserEntity()));

感谢返回给定的对象并且代码可以完全执行。您可以使用 failedFuture() 方法以类似的方式测试错误。


我创建了一个带有最小可重现示例的 GitHub 存储库 - 那里提供的测试通过了。


0
投票

我今天遇到了同样的问题。 @Jonasz 的答案仅在您可以模拟

getUserEntities()
getUser()
中的方法时才有效。但是,如果我们在这些方法中有逻辑并且仍然希望通过这些方法运行单元测试怎么办?

我的解决方案是使用静态模拟将

CompletableFuture.supplyAsync()
的行为从异步更改为同步

mockStatic(CompletableFuture.class, invoca -> {
    if (invoca.getMethod().getName().equals("supplyAsync")) {
        Supplier<?> supplier = invoca.getArgument(0);
        return CompletableFuture.completedFuture(supplier.get());
    }
    return invoca.callRealMethod();
});

通过这个解决方案,我可以在异步块内运行代码并将其通用到所有

CompletableFuture
类型

要使用静态模拟,您需要启用模拟内联。 https://davidvlijmincx.com/posts/mockito_mock_static_method/

© www.soinside.com 2019 - 2024. All rights reserved.