In this demo, there is a Mockito spy
on a @Bean
DemoService
in a test class.
DemoService
has a single @Async
method someAsyncTask
.
In a @Test
method, someAsyncTask
is mocked to throw a RuntimeException
then called expecting this exception to be thrown.
This test fails.
When @EnableAsync
is removed, this test passes.
Note: this issue was initially opened as a Spring Boot issue.
Comment From: redzi
I believe the issue is caused by DemoService being a proxy bean (because of @Async and how it's handled in Spring AOP). So mocking is done on a different object.
This test mocks the target object not the proxy and it passes:
@Test
void whenThenThrowTest() throws InterruptedException, ExecutionException {
DemoService targetObject = (DemoService) AopTestUtils.getTargetObject(demoService);
when(targetObject.someAsyncTask()).thenThrow(RuntimeException.class);
Assertions.assertThrows(RuntimeException.class, () -> targetObject.someAsyncTask());
}
Both test cases in the demo project are also ok when @EnableAsync is set with AspectJ mode:
@EnableAsync(mode = AdviceMode.ASPECTJ)
It works because there is no proxy involved, instead aspects are weaved directly into the code.
Comment From: snicoll
Correct and creating a spy behind the proxy like that is not going to work as you expected. Andy already provided the explanation in the Spring Boot issue:
The other part of the problem is that demoService.someAsyncTask() is being called on the proxy that's created to support @Async and not on the spy. This can be overcome by getting the proxy's target.