在@SpringBootTest 中测试@Async 注释方法

Testing @Async annotated method in @SpringBootTest

我有一个服务 SomeService,用一种方法来执行一些逻辑。

@Override
public CompletableFuture<Boolean> process(User user) {
    Objects.requiredNonNull(user, "user must not be null");
    // other logic...
}

那我有一个测试。

@RunWith(SpringRunner.class)
@SpringBootTest(classes = { SomeService.class })
public class SomeServiceTest {
    @Autowired private SomeService tested;
    @Test
    public void user_null_expect_NullPointerException() {
        assertThatThrownBy(() -> tested.process(null))
                .isInstanceOf(NullPointerException.class)
                .hasMessage("user must not be null");
    }
}

在我决定将该方法设为异步之前,它运行良好。

@Async
@Override
public CompletableFuture<Boolean> process(User user) {
    Objects.requiredNonNull(user, "user must not be null");
    // other logic...
}

因此,由于 Spring 代理,现在它不起作用。 有谁知道我必须如何配置测试才能使其再次运行?

好的,我有办法。问题不在于异步方法,问题在于错误的断言。我不知道 AssertJ 能够测试 CompletableFuture。

所以我的解决方案是这样的:

@Test
public void user_null_expect_NullPointerException() {
    final CompletableFuture<Boolean> result = getCompletedResult(null);

    assertThat(result)
            .isCompletedExceptionally()
            .hasFailedWithThrowableThat()
            .isInstanceOf(NullPointerException.class)
            .hasMessage("user must not be null");
}

private CompletableFuture<Boolean> getCompletedResult(User user) {
    final CompletableFuture<Boolean> result = tested.process(user);
    await().atMost(10, TimeUnit.SECONDS).until(result::isDone);
    return result;
}

如果您有更好的解决方案,请告诉我。

当您对服务 class 进行单元测试时,无需启动 Spring 上下文,但您可以直接实例化 class,避免由于 @Async。考试会更简单、更快。

@Async 属于框架,通常框架功能不应该在单元测试的范围内(而它们可能用于 component/integration 测试)。

也被Spring blog post推荐:

The easiest way to unit test any Spring @Component is to not involve Spring at all! It’s always best to try and structure your code so that classes can be instantiated and tested directly. Usually that boils down to a few things:

  • Structure your code with clean separation of concerns so that individual parts can be unit tested. TDD is a good way to achieve this.
  • Use constructor injection to ensure that objects can be instantiated directly. Don’t use field injection as it just makes your tests harder to write.