Reputation: 1790
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?
Upvotes: 2
Views: 9217
Reputation: 2257
As you are unit testing your service class, there is no need to start a Spring context but you can instantiate your class directly, avoiding the proxying due to @Async
. The test will be easier and faster.
@Async
belongs to the framework and usually framework features should not be in scope for unit testing (while they could be for component/integration testing).
Also suggested by a 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.
Upvotes: 0
Reputation: 1790
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.
Upvotes: 2