Search code examples
javaspring-bootunit-testingasynchronousassertj

Testing @Async annotated method in @SpringBootTest


I have a service SomeService with one method to do some logic.

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

Then I have a test for this.

@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");
    }
}

It worked fine until I decided to make that method asynchronous.

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

So, now it doesn't work because of Spring proxies. Does anyone have an idea how I must configure my test to make it work again?


Solution

  • Ok, I have a solution. The problem is not in async method, the problem is in wrong assertions. I didn't know AssertJ is able to test CompletableFuture.

    So my solution is this:

    @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;
    }
    

    If you have a better solution, let me know.