web-dev-qa-db-fra.com

Test de la méthode Spring MVC @ExceptionHandler avec le test Spring MVC

J'ai le contrôleur simple suivant pour attraper toute exception inattendue:

@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);
    }
}

J'essaie d'écrire un test d'intégration à l'aide de la structure de test Spring MVC. C'est ce que j'ai jusqu'ici:

@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"));
    }
}

J'inscris ExceptionController et un StatusController fictif dans l'infrastructure Spring MVC . Dans la méthode de test, j'ai configuré une attente de levée d'une exception à partir de StatusController.

L'exception est levée, mais ExceptionController ne s'en occupe pas.

Je veux pouvoir vérifier que ExceptionController obtient des exceptions et renvoie une réponse appropriée.

Avez-vous des idées sur les raisons pour lesquelles cela ne fonctionne pas et sur la manière dont je devrais effectuer ce type de test?

Merci.

34
C0deAttack

Je viens d'avoir le même problème et les travaux suivants pour moi:

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

Ce code ajoutera la possibilité d'utiliser vos avis contrôlés des exceptions.

@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;
}
4
Leszek Gruchała

Puisque vous utilisez le test de configuration autonome, vous devez fournir manuellement le gestionnaire d'exceptions.

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

J'ai eu le même problème quelques jours en arrière, vous pouvez voir mon problème et ma solution résolus par moi-même ici Spring MVC Controller Exception Test

En espérant que ma réponse vous aide

2
Agung Setiawan

Utilisez Spring MockMVC pour émuler un servletContainer à un point où vous pouvez incorporer tous les tests de filtrage des demandes ou de gestion des exceptions dans votre suite de tests unitaires.

Vous pouvez configurer cette configuration avec l’approche suivante:

Étant donné une exception RecordNotFound personnalisée ... 

@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);
    }
}

... et un 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());
    }
   ...
}

Configurer un contexte de test personnalisé: définissez une @ContextConfiguration pour spécifier les classes dont vous avez besoin pour votre test. Définissez Mockito MockMvc en tant qu'émulateur de conteneur de servlet et définissez votre équipement de test et vos dépendances.

 @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() {
    }
   ....

Configurez un mock MVC "émulateur de servlet": inscrivez les beans gestionnaires dans le contexte et générez l'émulateur mockMvc (Remarque: il existe deux configurations possibles: standaloneSetup ou webAppContextSetup; reportez-vous à la documentation ). Le générateur implémente à juste titre le modèle Builder afin que vous puissiez chaîner les commandes de configuration pour les résolveurs d'exceptions et les gestionnaires avant d'appeler 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");
    }
    ....

Exécuter vos 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());
    }
    ....
}

J'espère que cela t'aides.

Jake.

1
softjake

C'est mieux:

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

Et n'oubliez pas de rechercher les beans @ControllerAdvice dans votre classe @Configuration:

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

... testé sur Spring 4.0.2.LELEASE

0
Adam Dec