«1. Обзор

В этой короткой статье мы увидим, как должны создаваться исключения в наших контроллерах и как тестировать эти исключения с помощью Spring MockMvc.

2. Генерация исключений в контроллерах

Давайте начнем изучать, как запускать исключения из контроллера.

Мы можем думать о службах, которые мы предоставляем из контроллера, так же, как если бы они были обычными функциями Java:

@GetMapping("/exception/throw")
public void getException() throws Exception {
    throw new Exception("error");
}

Теперь давайте посмотрим, что происходит, когда мы вызываем эту службу. Во-первых, мы заметим, что код ответа службы — 500, что означает внутреннюю ошибку сервера.

Во-вторых, мы получаем тело ответа, подобное этому:

{
    "timestamp": 1592074599854,
    "status": 500,
    "error": "Internal Server Error",
    "message": "No message available",
    "trace": "java.lang.Exception
              at com.baeldung.controllers.ExceptionController.getException(ExceptionController.java:26)
              ..."
}

В заключение, когда мы выбрасываем исключение из RestController, ответ службы автоматически сопоставляется с кодом ответа 500, а трассировка стека исключения включается в тело ответа.

3. Сопоставление исключений с кодами ответов HTTP

Теперь мы узнаем, как сопоставлять наши исключения с другими кодами ответов, отличными от 500.

Для этого мы создадим собственные исключения и используйте аннотацию ResponseStatus, предоставленную Spring. Давайте создадим эти пользовательские исключения:

@ResponseStatus(HttpStatus.BAD_REQUEST)
public class BadArgumentsException extends RuntimeException {

    public BadArgumentsException(String message) {
        super(message);
    }
}
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public class InternalException extends RuntimeException {

    public InternalException(String message) {
        super(message);
    }
}
@ResponseStatus(HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {

    public ResourceNotFoundException(String message) {
        super(message);
    }
}

@GetMapping("/exception/{exception_id}")
public void getSpecificException(@PathVariable("exception_id") String pException) {
    if("not_found".equals(pException)) {
        throw new ResourceNotFoundException("resource not found");
    }
    else if("bad_arguments".equals(pException)) {
        throw new BadArgumentsException("bad arguments");
    }
    else {
        throw new InternalException("internal error");
    }
}

Второй и последний шаг — создать простую службу в нашем контроллере, чтобы генерировать эти исключения:

    Теперь давайте посмотрим на разные ответы. службы для различных исключений, которые мы сопоставили:

Для not_found мы получаем код ответа 404. При значении bad_arguments мы получаем код ответа 400. Для любого другого значения мы по-прежнему получаем код ответа 500 ~ ~~ Помимо кодов ответа, мы получим тело в том же формате, что и тело ответа, полученное в предыдущем разделе.

4. Тестирование наших контроллеров

Наконец, мы увидим, как проверить, что наш контроллер генерирует правильные исключения.

Первым шагом является создание тестового класса и создание экземпляра MockMvc:

@Autowired
private MockMvc mvc;

Далее давайте создадим тестовые примеры для каждого из значений, которые может принимать наш сервис:

@Test
public void givenNotFound_whenGetSpecificException_thenNotFoundCode() throws Exception {
    String exceptionParam = "not_found";

    mvc.perform(get("/exception/{exception_id}", exceptionParam)
      .contentType(MediaType.APPLICATION_JSON))
      .andExpect(status().isNotFound())
      .andExpect(result -> assertTrue(result.getResolvedException() instanceof ResourceNotFoundException))
      .andExpect(result -> assertEquals("resource not found", result.getResolvedException().getMessage()));
}

@Test
public void givenBadArguments_whenGetSpecificException_thenBadRequest() throws Exception {
    String exceptionParam = "bad_arguments";

    mvc.perform(get("/exception/{exception_id}", exceptionParam)
      .contentType(MediaType.APPLICATION_JSON))
      .andExpect(status().isBadRequest())
      .andExpect(result -> assertTrue(result.getResolvedException() instanceof BadArgumentsException))
      .andExpect(result -> assertEquals("bad arguments", result.getResolvedException().getMessage()));
}

@Test
public void givenOther_whenGetSpecificException_thenInternalServerError() throws Exception {
    String exceptionParam = "dummy";

    mvc.perform(get("/exception/{exception_id}", exceptionParam)
      .contentType(MediaType.APPLICATION_JSON))
      .andExpect(status().isInternalServerError())
      .andExpect(result -> assertTrue(result.getResolvedException() instanceof InternalException))
      .andExpect(result -> assertEquals("internal error", result.getResolvedException().getMessage()));
}

С эти тесты, мы проверяем, что код ответа, тип возбужденного исключения и сообщения об этих исключениях являются ожидаемыми для каждого из значений.

5. Заключение

В этом руководстве мы узнали, как обрабатывать исключения в наших Spring RestControllers и как проверять, что каждая открытая служба генерирует ожидаемые исключения.

Как всегда, полный исходный код статьи доступен на GitHub.