1

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?
balteo
  • 23,602
  • 63
  • 219
  • 412
  • 2
    It looks like `preferenceService` is a mock, but where do you stub `sendPasswordResetInfo` to provide non-`null` return value? – axtavt Jun 22 '14 at 00:25
  • Thanks. The test is actually an integration test and preferenceService is a full object. – balteo Jun 22 '14 at 07:43
  • 1
    If so, why do you stub `preferenceService.isEmailAvailable()`? – axtavt Jun 22 '14 at 11:08
  • @axtavt: Sorry. You're right. Bear with me. – balteo Jun 22 '14 at 11:15
  • @axtavt: You're very right: I had forgotten to stub sendPasswordResetInfo. Adding this: `when(preferenceService.sendPasswordResetInfo(anyString())).thenReturn(new AsyncResult(null));` sorted the issue. Thank you very much! – balteo Jun 22 '14 at 11:20

0 Answers0