简体   繁体   中英

Testing REST endpoints with custom exception handling

I am working on a project with Spring microservices (modules) and I want to test my REST endpoint using MockMvc . My testing works fine for cases where the request is valid but it is not working when requesting a url that is invalid. By not working I mean my custom exception handler ( @ControllerAdvice ) does not get called, the exception gets thrown and the test fails.

My exception handler and testing class are implemented in different modules.

common-module (ExceptionHandler)

@ControllerAdvice
public class CoreExceptionHandler {

    @ExceptionHandler(value = Exception.class)
    public ResponseEntity<ErrorMessageDTO> handleException(Exception ex, HttpServletRequest request) {

        // Getting servlet request URL
        String uri = request.getRequestURI();
        HttpStatus a;

        ErrorMessageDTO errorMessage;

        if (ex instanceof CoreException) {
            CoreException e = (CoreException) ex;
            ...
            errorMessage = new ErrorMessageDTO(e, uri);
        } else {
            errorMessage = new ErrorMessageDTO(ex, uri);
            ...
        }
        return new ResponseEntity<ErrorMessageDTO>(errorMessage, a);
    }
}

country-module

This is where my REST endpoint and Testing class are implemented. The common module dependency is included in this module's pom.xml and the packages are scanned through the main class.

CountryApplication.java

@EnableCaching
@EnableDiscoveryClient
@EnableAspectJAutoProxy
@SpringBootApplication(scanBasePackages = {
    "com.something1.something2.something3.common.exception",
    "com.something1.something2.something3.common.util.logged",
    "com.something1.something2.something3.country"
})
public class CountryApplication {
    public static void main(String[] args) throws Exception {
        SpringApplication.run(CountryApplication.class, args);
    }
    ...
}

CountryService.java

This is a method in my Service class.

@GetMapping("/{id:\\d+}")
public CountryDTO getCountryById(@PathVariable("id") Integer id) throws CoreException {

    Country countryEntity = this.countryRepository.findOne(id);

    // requesting for id that does not exist
    if (countryEntity == null) {
        throw new CoreException(CoreError.ENTITY_NOT_FOUND);
    }
    return this.countryMapper.daoToDto(countryEntity);
}

CountryServiceTest.java

@SpringBootTest
@AutoConfigureMockMvc
@AutoConfigureTestDatabase
@RunWith(SpringRunner.class)
public class CountryServiceTest {
    ...
    @Autowired
    private MockMvc mockMvc;

    @Test
    public void getByIdTest() throws Exception {

        // Get by id exists
        mockMvc.perform(get("/2"))
            .andExpect(status().isOk())
            .andExpect(content().contentType(contentType))
            .andDo(print());

        // Get by id not exists. NOT WORKING
          mockMvc.perform(get("/100000"))
            .andExpect(status().isNotFound())
            .andExpect(content().contentType(contentType));

    }
}

As I described above, the problem is that at the second request of the test method, the CoreExceptionHandler does not get called and the test fails throwing a:

NestedServletException: Request processing failed; nested exception is com.something1.something2.something3.common.exception.CoreException NestedServletException: Request processing failed; nested exception is com.something1.something2.something3.common.exception.CoreException .

The dependency for the common module is well configured (at least when I am deploying in non-test mode) since I am using it for other things too, plus the ExceptionHandler gets called when I am not testing.

Another strange thing is that when I am deploying my Test, Spring Boot's logs show that the CoreExceptionHandler gets detected. This is the line. Detected @ExceptionHandler methods in coreExceptionHandler

There are two problems as explained below:

(1) ControllerAdvice not being set for MockMvc object in your CountryServiceTest class, which can be done as shown below:

MockMvc mockMvc = standaloneSetup(yourController)
        .setHandlerExceptionResolvers(new CoreExceptionHandler())
        .build();

(2) Because CoreException is wrapper by NestedServletException by the Spring Container, you need to use exception.getCause() to check your exception as shown below:

@ControllerAdvice
    public class CoreExceptionHandler {

        @ExceptionHandler(value = Exception.class)
        public ResponseEntity<ErrorMessageDTO> handleException(Exception ex, 
            HttpServletRequest request) {

            // Getting servlet request URL
            String uri = request.getRequestURI();
            HttpStatus a;

            ErrorMessageDTO errorMessage;

            //check with exception cause
            if (ex.getCause() instanceof CoreException) {
                CoreException e = (CoreException) ex;
                ...
                errorMessage = new ErrorMessageDTO(e, uri);
            } else if (ex instanceof CoreException) {
               //this block will be used only when direct CoreException triggered
                CoreException e = (CoreException) ex;
                ...
                errorMessage = new ErrorMessageDTO(e, uri);
            } else {
                errorMessage = new ErrorMessageDTO(ex, uri);
                ...
            }
            return new ResponseEntity<ErrorMessageDTO>(errorMessage, a);
        }
    }

Also, I suggest not to handle all exception types in a single generic method, which will be very hard to support/maintain, rather split your CoreExceptionHandler using multiple @ExceptionHandler methods/classes.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM