How cover a ControllerAdvice dedicated to handle exception - java

I am having problems testing my HandleException, even though I have searched I only find solutions for a standard controller that integrates error management but I have it separately.
The problem is that when it comes to doing the unit tests and its coverage, I don't know how to cover it properly.
#Order(Ordered.HIGHEST_PRECEDENCE)
#ControllerAdvice
public class RestExceptionHandler {
#ExceptionHandler(AmazonClientException.class)
protected ResponseEntity<Object> handleAmazonClientException(AmazonClientException ex) {
return buildResponseEntity(new ApiError(HttpStatus.INTERNAL_SERVER_ERROR, ex));
}
#ExceptionHandler(AmazonS3Exception.class)
protected ResponseEntity<Object> handleAmazonS3Exception(AmazonS3Exception ex) {
return buildResponseEntity(new ApiError(HttpStatus.NOT_FOUND, ex));
}
private ResponseEntity<Object> buildResponseEntity(ApiError apiError) {
return new ResponseEntity<>(apiError, apiError.getStatus());
}
}
RestExceptionHandler.class
ApiError.class
#Data
public class ApiError {
private HttpStatus status;
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy hh:mm:ss")
private LocalDateTime timestamp;
private String message;
private String debugMessage;
private ApiError() {
setTimestamp(LocalDateTime.now());
}
ApiError(HttpStatus status) {
this();
setStatus(status);
}
ApiError(HttpStatus status, Throwable ex) {
this();
setStatus(status);
setMessage("Unexpected error");
setDebugMessage(ex.getLocalizedMessage());
}
ApiError(HttpStatus status, String message, Throwable ex) {
this();
setStatus(status);
setMessage(message);
setDebugMessage(ex.getLocalizedMessage());
}
}
How can see, i only use my RestExceptionHandler for that, not call another Business class.
Any advice appreciated.

To unit test #ControllerAdvice annotated classes, you can use something called MockMvc which Spring provides.
It is a very elegant way of unit testing your controllers and subsequent controller advices.
So, let's say this is my controller
#RestController
#RequestMapping("api")
public class RobotController {
private RobotService robotService;
#GetMapping
public Collection<Integer> get() {
throw new DemoException();
}
}
And here's my controller advice; DemoException is just an empty class that extends RuntimeException.
#RestControllerAdvice
public class RestExceptionHandler {
#ExceptionHandler(DemoException.class)
public ResponseEntity<Object> handleException(DemoException dex) {
return ResponseEntity
.status(400)
.body("Bad request");
}
}
So, this means, sending a GET request to BASEURL/api will give me Bad request text response with 400 status code.
Okay, since we are done with the setup, let's move to the actual unit test. This is a very simple unit test, but it does what you need.
public class AdviceTest {
private final MockMvc mockMvc = MockMvcBuilders
.standaloneSetup(new RobotController())
.setControllerAdvice(new RestExceptionHandler())
.build();
#Test
void testGetFails() {
mockMvc.perform(
MockMvcRequestBuilders.get("/api")
).andExpect(
status().isBadRequest()
);
}
}
So, let me explain the initialization of the MockMvc object. There are two versions.
Standalone setup in which you just provide manually created controller objects.
Web application context in which you just provide an autowired application context.
You can also autowire MockMvc object.
Out of those 2, I really like the former, because it does not include the whole Spring context, hence it is faster.
There are a lot of methods available from checking the json content to paths. You can read more here:
Spring Guide
Baeldung

Related

Best way to return ResponseEntity with data, empty and error in Spring Boot?

I am trying to implement a generic approach that can be used for each return types from Service to Controller in my Spring Boot app. After searching and reading several threads and articles, I see that I need an approach with #ControllerAdvice and #ExceptionHandler annotations.
For exception, I created the following class:
#ControllerAdvice
public class FileUploadException extends ResponseEntityExceptionHandler {
#SuppressWarnings("rawtypes")
#ExceptionHandler(MaxUploadSizeExceededException.class)
public ResponseEntity handleMaxSizeException(MaxUploadSizeExceededException e) {
return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED)
.body(new ResponseMessage("Too large!"));
}
}
However, I am not sure if it is ok and how I should treat Optional.empty() results in my Service and Controller. So, could you please suggest me a proper approach or give an example that is used for exception, result with data and result with Optional.empty() ?
Please note that I have the following articles, but they are too old and I am not sure if these approach are still good or there is a better approach with the new versions of Spring Boot, etc?
Guide to Spring Boot REST API Error Handling
Exception Handling in Spring MVC
Specific ExceptionHandler:
#RestController
#RequestMapping("/products")
public class ProductController {
#Autowierd private ProductService productService;
#GetMapping("/{code}")
public ResponseEntity<Product> findByCode(#PathVariable String code) {
return productService.findByCode(code).map(ResponseEntity::ok).orElseThrow(() -> NoContentException::new);
}
#ExceptionHandler(NoContentException.class)
public ResponseEntity handleNoContent(NoContentException e) {
return ResponseEntity.status(HttpStatus.NO_CONTENT).body(new ResponseMessage("No data"));
}
}
Or Common ExceptionHandler :
#RestController
#RequestMapping("/products")
public class ProductController {
#Autowierd private ProductService productService;
#GetMapping("/{code}")
public ResponseEntity<Product> findByCode(#PathVariable String code) {
return productService.findByCode(code).map(ResponseEntity::ok).orElseThrow(() -> NoContentException::new);
}
}
Plus :
#ControllerAdvice
public class CommonExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(MaxUploadSizeExceededException.class)
public ResponseEntity handleMaxSizeException(MaxUploadSizeExceededException e) {
return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).body(new ResponseMessage("Too large!"));
}
#ExceptionHandler(NoContentException.class)
public ResponseEntity handleNoContent(NoContentException e) {
return ResponseEntity.status(HttpStatus.NO_CONTENT).body(new ResponseMessage("No data"));
}
}

Getting 406 Could not find acceptable representation /Spring JSON Test. How to ignore .htm extension in tests?

Controller needs uses .htm extensions for all handlers, including JSON REST endpoints. How should I test for REST endpoints?
Problem:
I cannot disable suffix interpretation and I am getting 406 "Could not find acceptable representation"
Tried attempts:
I reviewed posts on stackoverflow related to 406, but could not find relevant one to the case where 'htm' suffix is used in tests. When you remove '.htm' suffix from both Controller and Test - the test is passing.
Here is controller with /changePassword.htm endpoint:
#Controller
public class MainController {
public static class ResultBean {
private final String result;
public String getResult() {
return result;
}
public ResultBean(String result) {
this.result = result;
}
}
#RequestMapping(value="/changePassword.htm", method= RequestMethod.POST, produces = { "application/json" })
public #ResponseBody ResultBean changePassword (
#RequestParam("username") String username, #RequestParam("password") String password) {
return new ResultBean("OK");
}
}
And here is the test with configuration:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(classes = { HomeControllerTest.Config.class })
public class HomeControllerTest {
#InjectMocks
private MainController controller = new MainController();
private MockMvc mvc;
#Configuration
#EnableWebMvc
public static class Config extends WebMvcConfigurerAdapter {
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false)
.favorParameter(true)
.parameterName("mediaType")
.ignoreUnknownPathExtensions(true)
.ignoreAcceptHeader(false)
.useJaf(false)
.defaultContentType(MediaType.APPLICATION_JSON);
}
#Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUseSuffixPatternMatch(false);
}
}
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
mvc = MockMvcBuilders.standaloneSetup(controller)
.build();
}
#Test
public void shouldPassChangePasswordBean() throws Exception {
mvc.perform(post("/changePassword.htm")
.accept("*/*")
.param("username", "example")
.param("password", "abcdef")
)
.andExpect(status().isOk()); // Test produces 406 instead of 200
}
}
Any idea?
On newer version of Spring (4+ I think), mime type is determined from suffix first.
So If you use a .htm suffix, Spring will default to produce HTML even if you don't want to.
One way to bypass this is to use a filter that rewrite URL. For instance tuckey URL rewriter filter
With this, you can set some rules like:
/my/page/that/return/json.htm is rewriten to /my/page/that/return/json so that Spring can produce data according to the Accept header.
with Spring 5, try changing your URL of your web service to .json! that is the right fix. great details here http://stick2code.blogspot.com/2014/03/solved-orgspringframeworkwebhttpmediaty.html

Can I know which controller was called in #ControllerAdvice?

now, I using the #ControllerAdvice in Spring 4.*.
using beforeBodyWrite method.
create custom annotation in controller class.
get the information of controller when #ControllerAdvice processing.
I want to know that request coming from what's controller class.
but, I don't know the solution.
any help.?
thanks
Although your question doesn't state clearly what is the purpose of what you are achieving and why do you need to create a custom annotation I'll post you some guidelines showing how to determine the source of your RuntimeException inside the ControllerAdvice
Given the following Rest controllers:
#RestController
public class CARestController {
#RequestMapping(value = "/test", method = RequestMethod.GET)
public String testException() {
throw new RuntimeException("This is the first exception");
}
}
#RestController
public class CAOtherRestController {
#RequestMapping(value = "/test-other", method = RequestMethod.GET)
public String testOtherException() {
throw new RuntimeException("This is the second exception");
}
}
Which both throw an exception, we can capture this exception by using the following ControllerAdvice and determine the origin of the exception using the stack trace.
#ControllerAdvice
public class CAControllerAdvice {
#ExceptionHandler(value = RuntimeException.class)
protected ResponseEntity<String> handleRestOfExceptions(RuntimeException ex) {
return ResponseEntity.badRequest().body(String.format("%s: %s", ex.getStackTrace()[0].getClassName(), ex.getMessage()));
}
}
This is how the output of the endpoints will look:
My recommendation is that instead of doing this, you declare your own set of exceptions and then capture them in your controller advice, with independence of where they were thrown:
public class MyExceptions extends RuntimeException {
}
public class WrongFieldException extends MyException {
}
public class NotFoundException extends MyException {
}
#ControllerAdvice
public class CAControllerAdvice {
#ExceptionHandler(value = NotFoundException .class)
public ResponseEntity<String> handleNotFound() {
/**...**/
}
#ExceptionHandler(value = WrongFieldException .class)
public ResponseEntity<String> handleWrongField() {
/**...**/
}
}

Spring boot - taking control of 404 Not Found

I'm trying to figure out the simplest way to take control over the 404 Not Found handler of a basic Spring Boot RESTful service such as the example provided by Spring:
https://spring.io/guides/gs/rest-service/
Rather than have it return the default Json output:
{
"timestamp":1432047177086,
"status":404,
"error":"Not Found",
"exception":"org.springframework.web.servlet.NoHandlerFoundException",
"message":"No handler found for GET /aaa, ..."
}
I'd like to provide my own Json output.
By taking control of the DispatcherServlet and using DispatcherServlet#setThrowExceptionIfNoHandlerFound(true), I was able to make it throw an exception in case of a 404 but I can't handle that exception through a #ExceptionHandler, like I would for a MissingServletRequestParameterException. Any idea why?
Or is there a better approach than having a NoHandlerFoundException thrown and handled?
It works perfectly Fine.
When you are using SpringBoot, it does not handle (404 Not Found) explicitly; it uses WebMvc error response. If your Spring Boot should handle that exception, then you should do some hack around Spring Boot. For 404, the exception class is NoHandlerFoundException; if you want to handle that exception in your #RestControllerAdvice class, you must add #EnableWebMvc annotation in your Application class and set setThrowExceptionIfNoHandlerFound(true); in DispatcherServlet. Please refer to the following code:
#SpringBootApplication
#EnableWebMvc
public class Application {
#Autowired
private DispatcherServlet servlet;
public static void main(String[] args) throws FileNotFoundException, IOException {
SpringApplication.run(Application.class, args);
}
#Bean
public CommandLineRunner getCommandLineRunner(ApplicationContext context) {
servlet.setThrowExceptionIfNoHandlerFound(true);
return args -> {};
}
}
After this you can handle NoHandlerException in your #RestControllerAdvice class
#RestControllerAdvice
public class AppException {
#ExceptionHandler(value={NoHandlerFoundException.class})
#ResponseStatus(code=HttpStatus.BAD_REQUEST)
public ApiError badRequest(Exception e, HttpServletRequest request, HttpServletResponse response) {
e.printStackTrace();
return new ApiError(400, HttpStatus.BAD_REQUEST.getReasonPhrase());
}
}
I have created ApiError class to return customized error response
public class ApiError {
private int code;
private String message;
public ApiError(int code, String message) {
this.code = code;
this.message = message;
}
public ApiError() {
}
//getter & setter methods...
}
According to the Spring documentation appendix A. there is a boolean property called spring.mvc.throw-exception-if-no-handler-found which can be used to enable throwing NoHandlerFoundException. Then you can create exception handler like any other.
#RestControllerAdvice
class MyExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(MyExceptionHandler.class);
#ResponseStatus(HttpStatus.NOT_FOUND)
#ExceptionHandler(NoHandlerFoundException.class)
public String handleNoHandlerFoundException(NoHandlerFoundException ex) {
log.error("404 situation detected.",ex);
return "Specified path not found on this server";
}
}
#ExceptionHandler itself without #ControllerAdvice (or #RestControllerAdvice) can't be used, because it's bound to its controller only.
In short, the NoHandlerFoundException is thrown from the Container, not from your application within your container. Therefore your Container has no way of knowing about your #ExceptionHandler as that is a Spring feature, not anything from the container.
What you want is a HandlerExceptionResolver. I had the very same issue as you, have a look at my solution over there: How to intercept "global" 404s on embedded Tomcats in spring-boot
The #EnableWebMvc based solution can work, but it might break Spring boot auto configurations.
The solution I am using is to implement ErrorController:
#RestController
#RequiredArgsConstructor
public class MyErrorController implements ErrorController {
private static final String ERROR_PATH = "/error";
#NonNull
private final ErrorAttributes errorAttributes;
#RequestMapping(value = ERROR_PATH)
Map<String, Object> handleError(WebRequest request) {
return errorAttributes.getErrorAttributes(request, false);
}
#Override
public String getErrorPath() {
return ERROR_PATH;
}
}
The solution for this problem is:
Configure DispatcherServlet to throw and exception if it doesn't find any handlers.
Provide your implementation for the exception that will be thrown from DispatcherServlet, for this case is the NoHandlerFoundException.
Thus, in order to configure DispatcherServlet you may use properties file or Java code.
Example for properties.yaml,
spring:
mvc:
throw-exception-if-no-handler-found: true
Example for properties.properties,
spring.mvn.throw-exception-if-no-handler-found=true
Example for Java code, we just want to run the command servlet.setThrowExceptionIfNoHandlerFound(true); on startup, I use the InitializingBean interface, you may use another way. I found a very well written guide to run logic on startup in spring from baeldung.
#Component
public class WebConfig implements InitializingBean {
#Autowired
private DispatcherServlet servlet;
#Override
public void afterPropertiesSet() throws Exception {
servlet.setThrowExceptionIfNoHandlerFound(true);
}
}
Be careful! Adding #EnableWebMvc disables autoconfiguration in Spring Boot 2, meaning that if you use the annotation #EnableWebMvc then you should use the Java code example, because the spring.mvc.* properties will not have any effect.
After configuring the DispatcherServlet, you should override the ResponseEntityExceptionHandler which is called when an Exception is thrown. We want to override the action when the NoHandlerFoundException is thrown, like the following example.
#ControllerAdvice
public class MyApiExceptionHandler extends ResponseEntityExceptionHandler {
#Override
public ResponseEntity<Object> handleNoHandlerFoundException(NoHandlerFoundException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
String responseBody = "{\"errormessage\":\"WHATEVER YOU LIKE\"}";
headers.add("Content-Type", "application/json;charset=utf-8");
return handleExceptionInternal(ex, responseBody, headers, HttpStatus.NOT_FOUND, request);
}
}
Finally, adding a break point to method handleException of ResponseEntityExceptionHandler might be helpful for debugging.

Testing Spring #MVC annotations

I ran into a problem the other day where a #Valid annotation was accidentally removed from a controller class. Unfortunately, it didn't break any of our tests. None of our unit tests actually exercise the Spring AnnotationMethodHandlerAdapter pathway. We just test our controller classes directly.
How can I write a unit or integration test that will correctly fail if my #MVC annotations are wrong? Is there a way I can ask Spring to find and exercise the relevant controller with a MockHttpServlet or something?
I write integration tests for this kind of thing. Say you have a bean with validation annotations:
public class MyForm {
#NotNull
private Long myNumber;
...
}
and a controller that handles the submission
#Controller
#RequestMapping("/simple-form")
public class MyController {
private final static String FORM_VIEW = null;
#RequestMapping(method = RequestMethod.POST)
public String processFormSubmission(#Valid MyForm myForm,
BindingResult result) {
if (result.hasErrors()) {
return FORM_VIEW;
}
// process the form
return "success-view";
}
}
and you want to test that the #Valid and #NotNull annotations are wired correctly:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({"file:web/WEB-INF/application-context.xml",
"file:web/WEB-INF/dispatcher-servlet.xml"})
public class MyControllerIntegrationTest {
#Inject
private ApplicationContext applicationContext;
private MockHttpServletRequest request;
private MockHttpServletResponse response;
private HandlerAdapter handlerAdapter;
#Before
public void setUp() throws Exception {
this.request = new MockHttpServletRequest();
this.response = new MockHttpServletResponse();
this.handlerAdapter = applicationContext.getBean(HandlerAdapter.class);
}
ModelAndView handle(HttpServletRequest request, HttpServletResponse response)
throws Exception {
final HandlerMapping handlerMapping = applicationContext.getBean(HandlerMapping.class);
final HandlerExecutionChain handler = handlerMapping.getHandler(request);
assertNotNull("No handler found for request, check you request mapping", handler);
final Object controller = handler.getHandler();
// if you want to override any injected attributes do it here
final HandlerInterceptor[] interceptors =
handlerMapping.getHandler(request).getInterceptors();
for (HandlerInterceptor interceptor : interceptors) {
final boolean carryOn = interceptor.preHandle(request, response, controller);
if (!carryOn) {
return null;
}
}
final ModelAndView mav = handlerAdapter.handle(request, response, controller);
return mav;
}
#Test
public void testProcessFormSubmission() throws Exception {
request.setMethod("POST");
request.setRequestURI("/simple-form");
request.setParameter("myNumber", "");
final ModelAndView mav = handle(request, response);
// test we're returned back to the form
assertViewName(mav, "simple-form");
// make assertions on the errors
final BindingResult errors = assertAndReturnModelAttributeOfType(mav,
"org.springframework.validation.BindingResult.myForm",
BindingResult.class);
assertEquals(1, errors.getErrorCount());
assertEquals("", errors.getFieldValue("myNumber"));
}
See my blog post on integration testing Spring's MVC annotations
Sure. There's no reason why your test can't instantiate its own DispatcherServlet, inject it with the various items which it would have in a container (e.g. ServletContext), including the location of the context definition file.
Spring comes with a variety of servlet-related MockXYZ classes for this purpose, including MockServletContext, MockHttpServletRequest and MockHttpServletResponse. They're not really "mock" objects in the usual sense, they're more like dumb stubs, but they do the job.
The servlet's test context would have the usual MVC-related beans, plus your beans to test. Once the servlet is initialized, create the mock requests and responses, and feed them into the servet's service() method. If request gets routed correctly, you can check the results as written to the mock response.
In upcoming spring 3.2 (SNAPSHOT available) or with spring-test-mvc (https://github.com/SpringSource/spring-test-mvc) you can do it like this:
first we emulate Validation as we do not want to test the validator, just want to know if validation is called.
public class LocalValidatorFactoryBeanMock extends LocalValidatorFactoryBean
{
private boolean fakeErrors;
public void fakeErrors ( )
{
this.fakeErrors = true;
}
#Override
public boolean supports ( Class<?> clazz )
{
return true;
}
#Override
public void validate ( Object target, Errors errors, Object... validationHints )
{
if (fakeErrors)
{
errors.reject("error");
}
}
}
this is our test class:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration
public class RegisterControllerTest
{
#Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
#Autowired
#InjectMocks
private RegisterController registerController;
#Autowired
private LocalValidatorFactoryBeanMock validator;
#Before
public void setup ( )
{
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
// if you want to inject mocks into your controller
MockitoAnnotations.initMocks(this);
}
#Test
public void testPostValidationError ( ) throws Exception
{
validator.fakeErrors();
MockHttpServletRequestBuilder post = post("/info/register");
post.param("name", "Bob");
ResultActions result = getMockMvc().perform(post);
// no redirect as we have errors
result.andExpect(view().name("info/register"));
}
#Configuration
#Import(DispatcherServletConfig.class)
static class Config extends WebMvcConfigurerAdapter
{
#Override
public Validator getValidator ( )
{
return new LocalValidatorFactoryBeanMock();
}
#Bean
RegisterController registerController ( )
{
return new RegisterController();
}
}
}

Categories

Resources