javaunit-testingasynchronous

Testing Async (ApplicationModuleListener) exception throwing


I am trying to test an async listener method - annotated with @ApplicationModuleListener (this is important for other aspects of an app, so this itself is not any problem), which I see is also annotated with @Async.
I have following code:

@Service
public class MyService {
...
@ApplicationModuleListener
public void onEvent(Event event) {
if(event.getSomeField().equals(someExpectedValue)) {
// do some expected code
}
else {
throw new UnexpectedValueException("fail!");
}
}

and testing it:

@SpringBootTest
class MyServiceTest {
@Autowired
MyService myService;

@Test
void testExceptionThrow {
Event event = new Event("someUnexpectedValue");

assertThrows(UnexpectedValueException.class, () -> myService.onEvent(event));
}
}

This test fails, saying that no exception was thrown. When I'm debugging the test, it shows that during runtime the else clause is being reached, and an exception is being thrown - but from that point, it is being consumed by multiple spring async mechanisms. Finally it seems the exception is being consumed at AsyncExecutionInterceptor.invoke() level, and does not reach back to my test method which called it in the first place.

Is there any way of testing the exception scenario for the event listener method, other than mocking the throw using Mockito's when().thenThrow()?


Solution

  • In the above example you are trying to combine unit test with Spring Boot Test. You have to follow one of them depending of your expectations:

    Unit test

    for unit test you have to init your tested class and you don't need Spring test related annotations:

    class MyServiceTest {
    
    MyService myService = new MyService() //logic of init, which can also go to @BeforeEach method also
    
    @Test
    void testExceptionThrow {
    Event event = new Event("someUnexpectedValue");
    
    assertThrows(UnexpectedValueException.class, () -> myService.onEvent(event));
     }
    }
    

    Integration test

    for integration test you have to use @ApplicationModuleTest as per above blog

    <dependency>
      <groupId>org.springframework.modulith</groupId>
      <artifactId>spring-modulith-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
    
    @ApplicationModuleTest
    class MyServiceTest {
    
    @Test
    void testExceptionThrow(Scenario scenario) {
     scenario.publish(new Event("someUnexpectedValue"))
      .andWaitAtMost(Duration.ofSeconds(10)))
      .andWaitForEventOfType(UnexpectedValueException.class)
    
     }
    }