8

My problem is connected with testing Spring @RestController which is also using @ControllerAdvice with @ExceptionHandler. Here is the code:

@ControllerAdvice class:

@ControllerAdvice
public class MyAppExceptionHandler {

    @ExceptionHandler({ NoSuchEntityException.class })
    @ResponseStatus(value = HttpStatus.NOT_FOUND)
    public @ResponseBody
    ErrorDTO handleNotFoundException(Exception ex) throws IOException {

        return new ErrorDTO.Builder().setStatus(HttpStatus.NOT_FOUND)
                .setCause(ex.getClass().getName())
                .setThrowable(ex).build();
    }
}

When using it in application everything works fine - perfectly getting 404 response with JSON explanation, but when trying to use it during tests - bad things happen.

My test class:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { WebConfig.class })
@WebAppConfiguration
public class SomeTest {

    @Mock
    private SomeService service;

    @InjectMocks
    private SomeController controller;

    private MockMvc mvc;

    private ExceptionHandlerExceptionResolver createExceptionResolver() {
        ExceptionHandlerExceptionResolver exceptionResolver = new ExceptionHandlerExceptionResolver() {
            @Override
            protected ServletInvocableHandlerMethod getExceptionHandlerMethod(
                HandlerMethod handlerMethod, Exception exception) {
                Method method = new ExceptionHandlerMethodResolver(
                        MyAppExceptionHandler.class).resolveMethod(exception);
                return new ServletInvocableHandlerMethod(
                    new MyAppExceptionHandler(), method);
            }
        };
        exceptionResolver.afterPropertiesSet();
        return exceptionResolver;
    }

    @Before
    public void setup() {

        MockitoAnnotations.initMocks(this);
        mvc = MockMvcBuilders.standaloneSetup(controller)
                .setHandlerExceptionResolvers(createExceptionResolver())
                .build();
    }

    @Test
    public void thatExceptionHappens() throws Exception {

        when(service.get(10)).thenThrow(new NoSuchEntityException(Some.class, 10));

        mvc.perform(get("/api/some/10")).andExpect(status().isNotFound());
    }
}

When trying to run it:

2014-07-15 19:35:01.376 [main] ERROR com.package.SomeTest$1 - Failed to invoke @ExceptionHandler method: public com.package.ErrorDTO com.package.MyAppExceptionHandler.handleNotFoundException(java.lang.Exception) throws java.io.IOException
org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation

I think that probably MappingJackson2HttpMessageConverter is not being loaded during testing mine @ExceptionHandler (however it is configured in WebConfig.class and when trying to perform typical test - one not throwing any exception - everything works fine).

Thanks in advance for your help.

tep
  • 83
  • 1
  • 1
  • 5

2 Answers2

14

I'm not sure if this is the best solution for this (I'd like to hear from another one),
but this is how I fix this exactly issue you are facing:

Add this line to your createExceptionResolver():

exceptionResolver.getMessageConverters().add(
        new MappingJackson2HttpMessageConverter());

Something like this:

private ExceptionHandlerExceptionResolver createExceptionResolver() {
    ExceptionHandlerExceptionResolver exceptionResolver = new ExceptionHandlerExceptionResolver() {
        @Override
        protected ServletInvocableHandlerMethod getExceptionHandlerMethod(
            HandlerMethod handlerMethod, Exception exception) {
            Method method = new ExceptionHandlerMethodResolver(
                    MyAppExceptionHandler.class).resolveMethod(exception);
            return new ServletInvocableHandlerMethod(
                new MyAppExceptionHandler(), method);
        }
    };
    exceptionResolver.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
    exceptionResolver.afterPropertiesSet();
    return exceptionResolver;
}

For some reason that I don't know, Spring was not loading my MappingJackson2HttpMessageConverter.
That line fixed my problem.

Denis C de Azevedo
  • 6,276
  • 2
  • 32
  • 49
  • 3
    Beware that when you set the `HandlerExceptionResolvers` in your builder you will wipe all of the default ones and use yours in place of theirs. This can cause you issues if you have any tests that use validation annotations for example and expect the appropriate response from Spring. I've raise an issue on Spring requesting that they allow us to _add_ a resolver instead of overwriting: https://jira.spring.io/browse/SPR-12751 – Matt Byrne Feb 25 '15 at 04:57
  • 1
    Spent 3 hours today until I solved the problem with exceptionResolver.getMessageConverters().add(...) as you did. This is sick! By default only 4 converters are added within ExceptionHandlerExceptionResolver constructor and Jackson was not there. – Simon Logic Mar 21 '18 at 17:55
3

The MockMvcBuilders standaloneSetup is manual by definition meaning that rather than discovering Spring beans of interest like @ControllerAdvice in Spring configuration, it lets you manually set up what you need for the test. Instead the standaloneSetup should let you also manually register @ControllerAdvice beans. There is a ticket for making that happen. See https://jira.spring.io/browse/SPR-12751.

Rossen Stoyanchev
  • 4,910
  • 23
  • 26
  • Thanks to @Rossen for implementing this. Spring 4.2 is shipped with this improvement and *Spring Boot* 1.3.0 also has this since it has a dependency on Spring 4.2+. – Matt Byrne Nov 08 '15 at 03:07