Add WebFlux equivalent of ResponseEntityExceptionHandler

Closes gh-28665
This commit is contained in:
rstoyanchev
2022-07-13 10:52:35 +01:00
parent dfae4eec2d
commit 263811ecfa
5 changed files with 642 additions and 69 deletions

View File

@@ -16,11 +16,9 @@
package org.springframework.web.servlet.mvc.method.annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import jakarta.servlet.ServletException;
import org.junit.jupiter.api.Test;
@@ -36,7 +34,6 @@ import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindException;
import org.springframework.validation.MapBindingResult;
@@ -56,6 +53,7 @@ import org.springframework.web.context.request.async.AsyncRequestTimeoutExceptio
import org.springframework.web.context.support.StaticWebApplicationContext;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver;
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
@@ -65,19 +63,19 @@ import org.springframework.web.testfixture.servlet.MockServletConfig;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test fixture for {@link ResponseEntityExceptionHandler}.
* Unit tests for {@link ResponseEntityExceptionHandler}.
*
* @author Rossen Stoyanchev
*/
public class ResponseEntityExceptionHandlerTests {
private ResponseEntityExceptionHandler exceptionHandlerSupport = new ApplicationExceptionHandler();
private final ResponseEntityExceptionHandler exceptionHandler = new ApplicationExceptionHandler();
private DefaultHandlerExceptionResolver defaultExceptionResolver = new DefaultHandlerExceptionResolver();
private final DefaultHandlerExceptionResolver exceptionResolver = new DefaultHandlerExceptionResolver();
private MockHttpServletRequest servletRequest = new MockHttpServletRequest("GET", "/");
private MockHttpServletResponse servletResponse = new MockHttpServletResponse();
private final MockHttpServletResponse servletResponse = new MockHttpServletResponse();
private WebRequest request = new ServletWebRequest(this.servletRequest, this.servletResponse);
@@ -101,21 +99,19 @@ public class ResponseEntityExceptionHandlerTests {
@Test
public void httpRequestMethodNotSupported() {
List<String> supported = Arrays.asList("POST", "DELETE");
Exception ex = new HttpRequestMethodNotSupportedException("GET", supported);
ResponseEntity<Object> entity =
testException(new HttpRequestMethodNotSupportedException("GET", List.of("POST", "DELETE")));
ResponseEntity<Object> responseEntity = testException(ex);
assertThat(responseEntity.getHeaders().getAllow()).isEqualTo(Set.of(HttpMethod.POST, HttpMethod.DELETE));
assertThat(entity.getHeaders().getFirst(HttpHeaders.ALLOW)).isEqualTo("POST, DELETE");
}
@Test
public void handleHttpMediaTypeNotSupported() {
List<MediaType> acceptable = Arrays.asList(MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_XML);
Exception ex = new HttpMediaTypeNotSupportedException(MediaType.APPLICATION_JSON, acceptable);
ResponseEntity<Object> entity = testException(new HttpMediaTypeNotSupportedException(
MediaType.APPLICATION_JSON, List.of(MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_XML)));
ResponseEntity<Object> responseEntity = testException(ex);
assertThat(responseEntity.getHeaders().getAccept()).isEqualTo(acceptable);
assertThat(responseEntity.getHeaders().getAcceptPatch()).isEmpty();
assertThat(entity.getHeaders().getFirst(HttpHeaders.ACCEPT)).isEqualTo("application/atom+xml, application/xml");
assertThat(entity.getHeaders().getAcceptPatch()).isEmpty();
}
@Test
@@ -123,92 +119,80 @@ public class ResponseEntityExceptionHandlerTests {
this.servletRequest = new MockHttpServletRequest("PATCH", "/");
this.request = new ServletWebRequest(this.servletRequest, this.servletResponse);
List<MediaType> acceptable = Arrays.asList(MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_XML);
Exception ex = new HttpMediaTypeNotSupportedException(MediaType.APPLICATION_JSON, acceptable, HttpMethod.PATCH);
ResponseEntity<Object> entity = testException(
new HttpMediaTypeNotSupportedException(
MediaType.APPLICATION_JSON,
List.of(MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_XML),
HttpMethod.PATCH));
ResponseEntity<Object> responseEntity = testException(ex);
assertThat(responseEntity.getHeaders().getAccept()).isEqualTo(acceptable);
assertThat(responseEntity.getHeaders().getAcceptPatch()).isEqualTo(acceptable);
HttpHeaders headers = entity.getHeaders();
assertThat(headers.getFirst(HttpHeaders.ACCEPT)).isEqualTo("application/atom+xml, application/xml");
assertThat(headers.getFirst(HttpHeaders.ACCEPT)).isEqualTo("application/atom+xml, application/xml");
assertThat(headers.getFirst(HttpHeaders.ACCEPT_PATCH)).isEqualTo("application/atom+xml, application/xml");
}
@Test
public void httpMediaTypeNotAcceptable() {
Exception ex = new HttpMediaTypeNotAcceptableException("");
testException(ex);
testException(new HttpMediaTypeNotAcceptableException(""));
}
@Test
public void missingPathVariable() throws NoSuchMethodException {
Method method = getClass().getDeclaredMethod("handle", String.class);
MethodParameter parameter = new MethodParameter(method, 0);
Exception ex = new MissingPathVariableException("param", parameter);
testException(ex);
testException(new MissingPathVariableException("param",
new MethodParameter(getClass().getDeclaredMethod("handle", String.class), 0)));
}
@Test
public void missingServletRequestParameter() {
Exception ex = new MissingServletRequestParameterException("param", "type");
testException(ex);
testException(new MissingServletRequestParameterException("param", "type"));
}
@Test
public void servletRequestBindingException() {
Exception ex = new ServletRequestBindingException("message");
testException(ex);
testException(new ServletRequestBindingException("message"));
}
@Test
public void conversionNotSupported() {
Exception ex = new ConversionNotSupportedException(new Object(), Object.class, null);
testException(ex);
testException(new ConversionNotSupportedException(new Object(), Object.class, null));
}
@Test
public void typeMismatch() {
Exception ex = new TypeMismatchException("foo", String.class);
testException(ex);
testException(new TypeMismatchException("foo", String.class));
}
@Test
@SuppressWarnings("deprecation")
public void httpMessageNotReadable() {
Exception ex = new HttpMessageNotReadableException("message");
testException(ex);
testException(new HttpMessageNotReadableException("message"));
}
@Test
public void httpMessageNotWritable() {
Exception ex = new HttpMessageNotWritableException("");
testException(ex);
testException(new HttpMessageNotWritableException(""));
}
@Test
public void methodArgumentNotValid() throws Exception {
Exception ex = new MethodArgumentNotValidException(
testException(new MethodArgumentNotValidException(
new MethodParameter(getClass().getDeclaredMethod("handle", String.class), 0),
new MapBindingResult(Collections.emptyMap(), "name"));
testException(ex);
new MapBindingResult(Collections.emptyMap(), "name")));
}
@Test
public void missingServletRequestPart() {
Exception ex = new MissingServletRequestPartException("partName");
testException(ex);
testException(new MissingServletRequestPartException("partName"));
}
@Test
public void bindException() {
Exception ex = new BindException(new Object(), "name");
testException(ex);
testException(new BindException(new Object(), "name"));
}
@Test
public void noHandlerFoundException() {
ServletServerHttpRequest req = new ServletServerHttpRequest(
new MockHttpServletRequest("GET","/resource"));
Exception ex = new NoHandlerFoundException(req.getMethod().toString(),
req.getServletRequest().getRequestURI(),req.getHeaders());
testException(ex);
testException(new NoHandlerFoundException("GET", "/resource", HttpHeaders.EMPTY));
}
@Test
@@ -244,8 +228,11 @@ public class ResponseEntityExceptionHandlerTests {
resolver.setApplicationContext(ctx);
resolver.afterPropertiesSet();
IllegalStateException ex = new IllegalStateException(new ServletRequestBindingException("message"));
assertThat(resolver.resolveException(this.servletRequest, this.servletResponse, null, ex)).isNull();
ModelAndView mav = resolver.resolveException(
this.servletRequest, this.servletResponse, null,
new IllegalStateException(new ServletRequestBindingException("message")));
assertThat(mav).isNull();
}
@Test
@@ -287,18 +274,18 @@ public class ResponseEntityExceptionHandlerTests {
private ResponseEntity<Object> testException(Exception ex) {
try {
ResponseEntity<Object> responseEntity = this.exceptionHandlerSupport.handleException(ex, this.request);
ResponseEntity<Object> entity = this.exceptionHandler.handleException(ex, this.request);
// SPR-9653
if (HttpStatus.INTERNAL_SERVER_ERROR.equals(responseEntity.getStatusCode())) {
if (HttpStatus.INTERNAL_SERVER_ERROR.equals(entity.getStatusCode())) {
assertThat(this.servletRequest.getAttribute("jakarta.servlet.error.exception")).isSameAs(ex);
}
this.defaultExceptionResolver.resolveException(this.servletRequest, this.servletResponse, null, ex);
// Verify DefaultHandlerExceptionResolver would set the same status
this.exceptionResolver.resolveException(this.servletRequest, this.servletResponse, null, ex);
assertThat(entity.getStatusCode().value()).isEqualTo(this.servletResponse.getStatus());
assertThat(responseEntity.getStatusCode().value()).isEqualTo(this.servletResponse.getStatus());
return responseEntity;
return entity;
}
catch (Exception ex2) {
throw new IllegalStateException("handleException threw exception", ex2);