I have the following Spring MVC controller method:
@RequestMapping(value = "/sendPasswordReset", method = RequestMethod.POST, produces = "text/html")
public String sendPasswordResetInformation(@ModelAttribute @Validated({ ValidationGroups.PasswordReset.class }) PasswordResetInfo passwordResetInfo,
BindingResult bindingResult, Model model, final RedirectAttributes redirectAttributes, Locale locale) throws InterruptedException, ExecutionException {
if (preferenceService.isEmailAvailable(passwordResetInfo.getEmail())) {
bindingResult.rejectValue("email", "controller.preference.email_not_in_system");
}
if (bindingResult.hasErrors()) {
model.addAttribute("passwordResetInfo", passwordResetInfo);
return "preference/sendPasswordReset";
}
redirectAttributes.addFlashAttribute("flashMessage", messageSource.getMessage("controller.preference.password_reset_info_sent", null, locale));
Future<Void> future = preferenceService.sendPasswordResetInfo(passwordResetInfo.getEmail());//.get();//TODO is ".get()" ugly?
future.get();//NPE HERE!!
return "redirect:/preference/sendPasswordReset";
}
Here is the implementation of sendPasswordResetInfo
:
@Async
@Override
public Future<Void> sendPasswordResetInfo(String email) {
Assert.hasText(email);
Member member = memberRepository.findByEmail(email);
try {
mailerService.doMailPasswordResetInfo(member);
return new AsyncResult<Void>(null);
} catch (MessagingException | MailSendException e) {
log.error("MessagingException | MailSendException", e);
throw new MailerException("MessagingException | MailSendException");
}
}
Here is how I am trying to integration test the controller method:
@Test
public void sendPasswordResetShouldHaveNormalInteractions() throws Exception {
when(preferenceService.isEmailAvailable(anyString())).thenReturn(Boolean.FALSE);
mockMvc.perform(post("/preference/sendPasswordReset")//
.param("email", VALID_EMAIL))//
.andExpect(redirectedUrl("/preference/sendPasswordReset"))//
.andExpect(flash().attributeExists("flashMessage"))//
.andExpect(flash().attributeCount(1));
verify(preferenceService).sendPasswordResetInfo(eq(VALID_EMAIL));
reset(preferenceService);
}
I systematically get a NullPointerException (in tests) in the controller method because the future object is null here:
future.get()
However the controller method runs fine when I use the app (outside of tests).
I have tried using a sync task executor as follows (to no avail):
@Profile(Profiles.TEST)
@Configuration
@EnableAsync
public class FakeAsyncConfiguration implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
SyncTaskExecutor taskExecutor = new SyncTaskExecutor();
return taskExecutor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new SimpleAsyncUncaughtExceptionHandler();
}
}
My questions are:
- Why is the
Future
object always null during the integration tests and - How can I ensure it is not null during my integration tests?