Testing Spring MVC @ExceptionHandler method with Spring MVC Test
Asked Answered
S

7

82

I have the following simple controller to catch any unexpected exceptions:

@ControllerAdvice
public class ExceptionController {

    @ExceptionHandler(Throwable.class)
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    public ResponseEntity handleException(Throwable ex) {
        return ResponseEntityFactory.internalServerErrorResponse("Unexpected error has occurred.", ex);
    }
}

I'm trying to write an integration test using Spring MVC Test framework. This is what I have so far:

@RunWith(MockitoJUnitRunner.class)
public class ExceptionControllerTest {
    private MockMvc mockMvc;

    @Mock
    private StatusController statusController;

    @Before
    public void setup() {
        this.mockMvc = MockMvcBuilders.standaloneSetup(new ExceptionController(), statusController).build();
    }

    @Test
    public void checkUnexpectedExceptionsAreCaughtAndStatusCode500IsReturnedInResponse() throws Exception {

        when(statusController.checkHealth()).thenThrow(new RuntimeException("Unexpected Exception"));

        mockMvc.perform(get("/api/status"))
                .andDo(print())
                .andExpect(status().isInternalServerError())
                .andExpect(jsonPath("$.error").value("Unexpected Exception"));
    }
}

I register the ExceptionController and a mock StatusController in the Spring MVC infrastructure. In the test method I setup an expectation to throw an exception from the StatusController.

The exception is being thrown, but the ExceptionController isn't dealing with it.

I want to be able to test that the ExceptionController gets exceptions and returns an appropriate response.

Any thoughts on why this doesn't work and how I should do this kind of test?

Thanks.

Santee answered 21/5, 2013 at 11:52 Comment(5)
I guess in testing exception handlers don't get assigned, don't know why exactly but this is why it happens, look at this answer #11649536Faber
Any news about this? I am in same situation.Euchology
I didn't find a solution. I decided that I would trust the @ExceptionHandler works and since the method itself is simple I decided I could live without testing that annotation. You can still test the method with a regular unit test.Santee
Probably your exception extends Throwable instead of Exception. I was facing same issue and checked the code in InvocableHandlerMethod which checks for below else if (targetException instanceof Exception) { throw (Exception) targetException; }Constantin
Check if this solution helps. Replaced $.error with $.messageHourigan
D
116

I just had the same issue and the following works for me:

@Before
public void setup() {
    this.mockMvc = MockMvcBuilders.standaloneSetup(statusController)
         .setControllerAdvice(new ExceptionController())
        .build();
}
Davidadavidde answered 23/10, 2015 at 20:55 Comment(5)
While this works, it's a pain to have to manually inject all the dependencies. Shouldn't the @WebMvcTest annotation take the ControllerAdvice as a parameter?Epitomize
@StuartMcIntyre Yes SpringBootTest/WebMvcTest should be the preferred way to go. I rarely use standaloneSetup() in green field projects but if I remember correctly I was trying to add a small number of tests with minimal impact to an existing test suite.Davidadavidde
May God bless you with the highest salary. It is quite hard to find a simple and straight answer to the questions here nowadaysDismay
Just to improve a bit the answer (just the name 'ExceptionController' is misleading), you can use .setControllerAdvice(new GlobalExceptionHandler()) where GlobalExceptionHandler is a @ControllerAdvice annotated classGuardsman
also @Import(ExceptionHandler.class) worked for me.Leptosome
S
5

This code will add ability to use your exceptions controlled advice.

@Before
public void setup() {
    this.mockMvc = standaloneSetup(commandsController)
        .setHandlerExceptionResolvers(withExceptionControllerAdvice())
        .setMessageConverters(new MappingJackson2HttpMessageConverter()).build();
}

private ExceptionHandlerExceptionResolver withExceptionControllerAdvice() {
    final ExceptionHandlerExceptionResolver exceptionResolver = new ExceptionHandlerExceptionResolver() {
        @Override
        protected ServletInvocableHandlerMethod getExceptionHandlerMethod(final HandlerMethod handlerMethod,
            final Exception exception) {
            Method method = new ExceptionHandlerMethodResolver(ExceptionController.class).resolveMethod(exception);
            if (method != null) {
                return new ServletInvocableHandlerMethod(new ExceptionController(), method);
            }
            return super.getExceptionHandlerMethod(handlerMethod, exception);
        }
    };
    exceptionResolver.afterPropertiesSet();
    return exceptionResolver;
}
Shafer answered 28/11, 2014 at 20:11 Comment(0)
H
2

Since you are using stand alone setup test you need to provide exception handler manually.

mockMvc= MockMvcBuilders.standaloneSetup(adminCategoryController).setSingleView(view)
        .setHandlerExceptionResolvers(getSimpleMappingExceptionResolver()).build();

I had same problem a few days back, you can see my problem and solution answered by myself here Spring MVC Controller Exception Test

Hoping my answer help you out

Husted answered 22/8, 2013 at 0:58 Comment(0)
A
1

Use Spring MockMVC to emulate a servletContainer to a point where you can incorporate any request filtering or exception handling tests in your unit tests suite.

You can configure this setup with the following approach:

Given a custom RecordNotFound exception...

@ResponseStatus(value=HttpStatus.NOT_FOUND, reason="Record not found") //
public class RecordNotFoundException extends RuntimeException {

    private static final long serialVersionUID = 8857378116992711720L;

    public RecordNotFoundException() {
        super();
    }

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

... and a RecordNotFoundExceptionHandler

@Slf4j
@ControllerAdvice
public class BusinessExceptionHandler {

    @ExceptionHandler(value = RecordNotFoundException.class)
    public ResponseEntity<String> handleRecordNotFoundException(
            RecordNotFoundException e,
            WebRequest request) {
         //Logs
        LogError logging = new LogError("RecordNotFoundException",
                HttpStatus.NOT_FOUND, 
                request.getDescription(true));
        log.info(logging.toJson());

        //Http error message
        HttpErrorResponse response = new HttpErrorResponse(logging.getStatus(), e.getMessage());
        return new ResponseEntity<>(response.toJson(),
                HeaderFactory.getErrorHeaders(),
                response.getStatus());
    }
   ...
}

Configure a tailored test context: set a @ContextConfiguration to specify the classes you need for your test. Set Mockito MockMvc as a servlet container emulator and set your tests fixture and dependencies.

 @RunWith(SpringRunner.class)
@ContextConfiguration(classes = {
    WebConfig.class,
    HeaderFactory.class,
})
@Slf4j
public class OrganisationCtrlTest {

    private MockMvc mvc;

    private Organisation coorg;

    @MockBean
    private OrganisationSvc service;

    @InjectMocks
    private OrganisationCtrl controller = new OrganisationCtrl();

    //Constructor
    public OrganisationCtrlTest() {
    }
   ....

Configure a mock MVC "servlet emulator": register handler beans in the context and build the mockMvc emulator (Note: there are two possible configuration: standaloneSetup or webAppContextSetup; refer to the documentation). The builder rightfully implements the Builder pattern so you can chain configuration commands for exception resolvers and handlers before calling build().

    @Before
    public void setUp() {
        final StaticApplicationContext appContext = new StaticApplicationContext();
        appContext.registerBeanDefinition("BusinessExceptionHandler",
                new RootBeanDefinition(BusinessExceptionHandler.class, null, null));

//InternalExceptionHandler extends ResponseEntityExceptionHandler to //handle Spring internally throwned exception
        appContext.registerBeanDefinition("InternalExceptionHandler",
                new RootBeanDefinition(InternalExceptionHandler.class, null,
                        null));
        MockitoAnnotations.initMocks(this);
        mvc = MockMvcBuilders.standaloneSetup(controller)
                .setHandlerExceptionResolvers(getExceptionResolver(appContext))
                .build();
        coorg = OrganisationFixture.getFixture("orgID", "name", "webSiteUrl");
    }
    ....

Get the exception resolver

private ExceptionHandlerExceptionResolver getExceptionResolver(
        StaticApplicationContext context) {
    ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver();
    resolver.getMessageConverters().add(
            new MappingJackson2HttpMessageConverter());
    resolver.setApplicationContext(context);
    resolver.afterPropertiesSet();
    return resolver;
}

Run your tests

    @Test
    public void testGetSingleOrganisationRecordAnd404() throws Exception {
        System.out.println("testGetSingleOrganisationRecordAndSuccess");
        String request = "/orgs/{id}";
        log.info("Request URL: " + request);

        when(service.getOrganisation(anyString())).
                thenReturn(coorg);
        this.mvc.perform(get(request)
                .accept("application/json")
                .andExpect(content().contentType(
                        .APPLICATION_JSON))
                .andExpect(status().notFound())
                .andDo(print());
    }
    ....
}

Hope this helps.

Jake.

Anthraquinone answered 15/5, 2018 at 22:30 Comment(2)
which package contains getExceptionResolver() function in your code?Plastometer
@Plastometer Resolved the missing resolver getter. Thanks for raising the flag.Anthraquinone
S
1
@Before
public void setup() {
    this.mockMvc = MockMvcBuilders
         .standaloneSetup(YOUR_REST_CONTROLLER_INSTANCE)
         .setControllerAdvice(YOUR_EXCEPTION_HANDLER_INSTANCE)
         .build();
}
Squarrose answered 24/5 at 14:40 Comment(1)
Thank you for your interest in contributing to the Stack Overflow community. This question already has a few answers—including one that has been extensively validated by the community. Are you certain your approach hasn’t been given previously? If so, it would be useful to explain how your approach is different, under what circumstances your approach might be preferred, and/or why you think the previous answers aren’t sufficient. Can you kindly edit your answer to offer an explanation?Piperine
D
-2

Try it;

@RunWith(value = SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes = { MVCConfig.class, CoreConfig.class, 
        PopulaterConfiguration.class })
public class ExceptionControllerTest {

    private MockMvc mockMvc;

    @Mock
    private StatusController statusController;

    @Autowired
    private WebApplicationContext wac;

    @Before
    public void setup() {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
    }

    @Test
    public void checkUnexpectedExceptionsAreCaughtAndStatusCode500IsReturnedInResponse() throws Exception {

        when(statusController.checkHealth()).thenThrow(new RuntimeException("Unexpected Exception"));

        mockMvc.perform(get("/api/status"))
                .andDo(print())
                .andExpect(status().isInternalServerError())
                .andExpect(jsonPath("$.error").value("Unexpected Exception"));
    }
}
Dollarfish answered 5/2, 2014 at 21:23 Comment(0)
F
-2

This is better:

((HandlerExceptionResolverComposite) wac.getBean("handlerExceptionResolver")).getExceptionResolvers().get(0)

And do not forget to scan for @ControllerAdvice beans in your @Configuration class:

@ComponentScan(basePackages = {"com.company.exception"})

...tested on Spring 4.0.2.RELEASE

Flareup answered 23/2, 2016 at 7:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.