1

I'm testing my RestController with mockMvc. I have a global RestExceptionHandler to resolve all exceptions. In my RestController I throw custom Exception RequestValidationException like this:

    @ApiOperation("Search something")
    @RequestMapping(path = "/search", method = RequestMethod.POST)
    public CompletableFuture<SomeResponse> search(
            @RequestBody @Validated SearchRequest request, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            throw new RequestValidationException(bindingResult);
        }
        return searchService.search(request);
    }

And when i pass empty request it must throw RequestValidationException(bindingResult)

but when i start tests they fall in that place where i throw Exception instead to resolve it.

i try to configure my mockMvc like this:

@RunWith(SpringRunner.class)
public class SearchControllerTest {
    private MockMvc mockMvc;

    @InjectMocks
    protected SearchController searchController;

    @MockBean
    private SearchService searchService;

    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);
        this.mockMvc = MockMvcBuilders.standaloneSetup(searchController)
                .setHandlerExceptionResolvers(getHandlerExceptionResolver())
                .build();
    }

  private HandlerExceptionResolver getHandlerExceptionResolver() {
        final StaticApplicationContext applicationContext = new StaticApplicationContext();
        applicationContext.registerSingleton("exceptionHandler", RestExceptionHandler.class);

        final WebMvcConfigurationSupport webMvcConfigurationSupport = new WebMvcConfigurationSupport();
        webMvcConfigurationSupport.setApplicationContext(applicationContext);
        return webMvcConfigurationSupport.handlerExceptionResolver();
    }

but it doesnt help. i'm getting an Exception insted json with message.

My RequestValidationExceptionHandler:

@Component
public class RequestValidationExceptionHandler implements ApiExceptionHandler {

    @Override
    public ResponseEntity<ApiResponse> process(Throwable throwable) {
        RequestValidationException e = (RequestValidationException) throwable;

        if (e.getBindingResult() != null) {
            return new ResponseEntity<>(ApiResponse.badRequest(e.getBindingResult()), HttpStatus.OK);
        }

        return new ResponseEntity<>(ApiResponse.badRequest(throwable, ApiResponseCode.BAD_REQUEST), HttpStatus.OK);
    }

    @Override
    public Class<? extends Throwable> getSupportedException() {
        return RequestValidationException.class;
    }
}

2) My @ControllerAdvice:

@Slf4j
@ControllerAdvice
@SuppressWarnings({"checkstyle:JavadocMethod", "checkstyle:MultipleStringLiterals"})
public class RestExceptionHandler {

    @Autowired
    private ExceptionHandlerRegistry handlerRegistry;

    @ExceptionHandler
    public ResponseEntity handleThrowable(Throwable throwable, WebRequest request) {
        request.setAttribute(Constants.ERROR_ATTRIBUTE_NAME, throwable, RequestAttributes.SCOPE_REQUEST);

        Throwable ex = throwable instanceof CompletionException ?
                ObjectUtils.defaultIfNull(throwable.getCause(), throwable) : throwable;

        for (ApiExceptionHandler handler : handlerRegistry.getHandlers()) {
            if (handler.isSupported(ex)) {
                return handler.process(ex);
            }
        }

        return new ResponseEntity<>(ApiResponse.badRequest(throwable, ApiResponseCode.SERVER_ERROR), HttpStatus.OK);
    }
}

3) And ExceptionHandlerRegistry :

@Component
public class ExceptionHandlerRegistry {

    @Getter
    private final List<ApiExceptionHandler> handlers;

    @Autowired
    public ExceptionHandlerRegistry(List<ApiExceptionHandler> handlers) {
        this.handlers = ObjectUtils.defaultIfNull(handlers, Collections.emptyList());
    }
}

The Error message: org.springframework.web.util.NestedServletException: Request processing failed; nested exception is ru.filit.mvideo.mb2c.api.exceptions.RequestValidationException

UPDATE So after some discussion with @MichaelMichailidis, i try to do this, i just add an inner @Configuration class with needed beans:


    @TestConfiguration
    static class SearchControllerTestConfiguration {

        @Bean
        public RequestValidationExceptionHandler requestValidationExceptionHandler(){
            return new RequestValidationExceptionHandler();
        }

        @Bean
        public ExceptionHandlerRegistry getExceptionHandlerRegistry(final RequestValidationExceptionHandler requestValidationExceptionHandler){
            return new ExceptionHandlerRegistry(Collections.singletonList(requestValidationExceptionHandler));
        }

        @Bean
        public RestExceptionHandler getRestExceptionHandler(){
            return new RestExceptionHandler();
        }
    }

and my test pass. But i can't understand why test were working without configuration before i add @ControllerAdvice?

Michael Michailidis
  • 1,002
  • 1
  • 8
  • 21
  • Check the logs when the test starts. I have a feeling the spring context is not initialized and you need the @SpringBootTest annotation to fire the context of the spring ( if the context doesnt start then you are writting a unit test ) – Michael Michailidis Aug 28 '19 at 08:25
  • MichaelMichailidis, When i try @SpringBootTest it fails with message java.lang.IllegalStateException: Failed to load ApplicationContext – Хамидилло Мамытов Aug 28 '19 at 08:30
  • ye that was my bad .. was trying to edit the comment but seems 5 minutes is the limit for edit ! Add this annotation for your senario `@WebMvcTest` as you are mocking the web layer. The other annotation fires the whole context without mocking anything. Also have a look at this guide https://howtodoinjava.com/spring-boot2/spring-boot-mockmvc-example/ – Michael Michailidis Aug 28 '19 at 08:34
  • I'm sorry but all annotions according to create applicationContext fails with message " java.lang.IllegalStateException: Failed to load ApplicationContext" – Хамидилло Мамытов Aug 28 '19 at 08:45
  • ApplicationContext means it cannot initialize some beans due to missing configurations propably. It is the most general exception spring can throw as it explains more if you read the logs. To fix it for starters you have to make sure your application can start ( not with a test.. if you go to your main and run it. ) if it can start you need to figure the missconfiguration in your tests. Else you have to start by fixing the application itself to be runnable. Those two annotations I mentioned tell spring to run your whole application so it needs to be properly configured – Michael Michailidis Aug 28 '19 at 08:50
  • i have updated my post. thank you – Хамидилло Мамытов Aug 28 '19 at 09:06
  • 1
    Your tests stopped working due to the annotation you added to load the context. Without the context you were unit testing and thats why to advice wouldn't be triggered. After the annotation was added you changed the test into "integration" test and the whole context tried to load. So it required missing configurations – Michael Michailidis Aug 28 '19 at 09:24

1 Answers1

-1

You can try importing your exception handler in your test class:

@RunWith(SpringRunner.class)
@Import(RestExceptionHandler.class)  // EXCEPTION HANDLER CLASS
public class SearchControllerTest {
jordisan
  • 111
  • 1
  • 6