spring-bootspring-mvcspring-securitymockitospring-test

Test for Email Sending Failure returns HTTP 200 instead of 500


I'm writing a test case to verify that an EmailSendingException results in an HTTP 500 response. However, when the exception is thrown, my test still returns a status of 200 instead of the expected 500.

Here are the relevant parts of my code:

Test Case:

@Test
@WithMockUser(username = "admin", roles = {"ADMIN"})
void testSendBasicEmail_Failure() throws Exception {
    // Arrange
    BasicEmailRequest request = new BasicEmailRequest();
    request.setToEmail("test@example.com");
    request.setSubject("Test Subject");
    request.setBody("Test Body");
    request.setIsHtml(false);
    
    doThrow(new EmailSendingException("Failed to send email")).when(emailOperations)
            .sendBasicEmail(anyString(), anyList(), anyList(), anyString(), anyString(), anyBoolean());

    // Act & Assert
    mockMvc.perform(post("/api/email/sendBasic")
                    .with(csrf())
                    .contentType(MediaType.APPLICATION_JSON)
                    .content(objectMapper.writeValueAsString(request)))
            .andExpect(status().isInternalServerError()) // Expecting HTTP 500
            .andExpect(content().string("Failed to send email. Please try again later."));
    
    verify(emailOperations, times(1)).sendBasicEmail(
            "test@example.com", null, null, "Test Subject", "Test Body", false);
}

Controller:

@PostMapping("/sendBasic")
@PreAuthorize("hasRole('USER') or hasRole('ADMIN')")
public ResponseEntity<String> sendBasicEmail(@Valid @RequestBody BasicEmailRequest request) throws EmailSendingException {
    logger.info("Sending basic email: {}", request);
    emailOperations.sendBasicEmail(
            request.getToEmail(),
            request.getCcEmails(),
            request.getBccEmails(),
            request.getSubject(),
            request.getBody(),
            request.getIsHtml()
    );
    return ResponseEntity.ok("Email sent successfully.");
}

Exception Handling:

@ExceptionHandler(EmailSendingException.class)
public ResponseEntity<ErrorResponse> handleEmailSendingException(EmailSendingException ex) {
    logger.error("Email sending failed: {}", ex.getMessage(), ex);

    ErrorResponse errorResponse = new ErrorResponse("EMAIL_SENDING_FAILED", ex.getMessage());

    return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
}

Error:

The test fails with the following error message:

java.lang.AssertionError: Status expected:<500> but was:<200>
Expected :500
Actual   :200

I expect an HTTP 500 status when the EmailSendingException is thrown, but instead, I'm getting an HTTP 200 response with "Email sent successfully." What could be causing this, and how can I fix it?

Environment:

    Spring Boot: 3.3.4
    Spring Security
    Spring Test
    JUnit 5
    Mockito

Any advice on how to resolve this would be appreciated!


Solution

  • In your test, your ccEmails and bccEmails fields are null. However, ArgumentMatchers.anyList() does only match non-null lists, as mentioned by the API docs:

    Any non-null List.

    Since Mockito 2.1.0, only allow non-null List. As this is a nullable reference, the suggested API to match null wrapper would be isNull(). We felt this change would make test harnesses much safer than they were with Mockito 1.x.

    The result is that your exception is never thrown by the mock because it doesn't match. So as mentioned by the API docs, you could use isNull(). Alternatively, you could use any():

    doThrow(new EmailSendingException("Failed to send email"))
        .when(emailOperations)
        .sendBasicEmail(
            anyString(), 
            isNull(), // Change this
            isNull(), // Change this
            anyString(),
            anyString(),
            anyBoolean());
    

    My personal opinion is that since you already have a verify() statement confirming the exact call, I prefer using any() everywhere in the doThrow() statement. If you use the wrong matcher, you can detect it far easier with the verify() API than with when().