diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/ExceptionHandlerTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/ExceptionHandlerTests.java index d68c5253d59d..b50a488d335f 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/ExceptionHandlerTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/ExceptionHandlerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,12 +18,16 @@ import org.junit.Test; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.http.MediaType; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.RestControllerAdvice; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @@ -33,30 +37,78 @@ * Exception handling via {@code @ExceptionHandler} method. * * @author Rossen Stoyanchev + * @author Sam Brannen */ public class ExceptionHandlerTests { @Test - public void testExceptionHandlerMethod() throws Exception { + public void mvcLocalExceptionHandlerMethod() throws Exception { standaloneSetup(new PersonController()).build() - .perform(get("/person/Clyde")) + .perform(get("/person/Clyde")) .andExpect(status().isOk()) .andExpect(forwardedUrl("errorView")); } @Test - public void testGlobalExceptionHandlerMethod() throws Exception { + public void mvcGlobalExceptionHandlerMethod() throws Exception { standaloneSetup(new PersonController()).setControllerAdvice(new GlobalExceptionHandler()).build() .perform(get("/person/Bonnie")) .andExpect(status().isOk()) .andExpect(forwardedUrl("globalErrorView")); } + @Test + public void restNoException() throws Exception { + standaloneSetup(new RestPersonController()) + .setControllerAdvice(new RestGlobalExceptionHandler(), new RestPersonControllerExceptionHandler()).build() + .perform(get("/person/Yoda").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.name").value("Yoda")); + } + + @Test + public void restLocalExceptionHandlerMethod() throws Exception { + standaloneSetup(new RestPersonController()) + .setControllerAdvice(new RestGlobalExceptionHandler(), new RestPersonControllerExceptionHandler()).build() + .perform(get("/person/Luke").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.error").value("local - IllegalArgumentException")); + } + + @Test + public void restGlobalExceptionHandlerMethod() throws Exception { + standaloneSetup(new RestPersonController()) + .setControllerAdvice(new RestGlobalExceptionHandler()).build() + .perform(get("/person/Leia").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.error").value("global - IllegalStateException")); + } + + @Test + public void restGlobalRestPersonControllerExceptionHandlerTakesPrecedenceOverGlobalExceptionHandler() throws Exception { + standaloneSetup(new RestPersonController()) + .setControllerAdvice(new RestGlobalExceptionHandler(), new RestPersonControllerExceptionHandler()).build() + .perform(get("/person/Leia").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.error").value("globalPersonController - IllegalStateException")); + } + + @Test // gh-25520 + public void restNoHandlerFound() throws Exception { + standaloneSetup(new RestPersonController()) + .setControllerAdvice(new RestGlobalExceptionHandler(), new RestPersonControllerExceptionHandler()) + .addDispatcherServletCustomizer(dispatcherServlet -> dispatcherServlet.setThrowExceptionIfNoHandlerFound(true)) + .build() + .perform(get("/bogus").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.error").value("global - NoHandlerFoundException")); + } + @Controller private static class PersonController { - @RequestMapping(value="/person/{name}", method=RequestMethod.GET) + @GetMapping("/person/{name}") public String show(@PathVariable String name) { if (name.equals("Clyde")) { throw new IllegalArgumentException("simulated exception"); @@ -73,7 +125,6 @@ public String handleException(IllegalArgumentException exception) { } } - @ControllerAdvice private static class GlobalExceptionHandler { @@ -81,7 +132,73 @@ private static class GlobalExceptionHandler { public String handleException(IllegalStateException exception) { return "globalErrorView"; } + } + + @RestController + private static class RestPersonController { + + @GetMapping("/person/{name}") + Person get(@PathVariable String name) { + switch (name) { + case "Luke": + throw new IllegalArgumentException(); + case "Leia": + throw new IllegalStateException(); + default: + return new Person("Yoda"); + } + } + + @ExceptionHandler + Error handleException(IllegalArgumentException exception) { + return new Error("local - " + exception.getClass().getSimpleName()); + } + } + + @RestControllerAdvice(assignableTypes = RestPersonController.class) + @Order(Ordered.HIGHEST_PRECEDENCE) + private static class RestPersonControllerExceptionHandler { + + @ExceptionHandler + Error handleException(Throwable exception) { + return new Error("globalPersonController - " + exception.getClass().getSimpleName()); + } + } + + @RestControllerAdvice + @Order(Ordered.LOWEST_PRECEDENCE) + private static class RestGlobalExceptionHandler { + + @ExceptionHandler + Error handleException(Throwable exception) { + return new Error( "global - " + exception.getClass().getSimpleName()); + } + } + static class Person { + + private final String name; + + Person(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + static class Error { + + private final String error; + + Error(String error) { + this.error = error; + } + + public String getError() { + return error; + } } }