【问题标题】:Testing Spring MVC @ExceptionHandler method with Spring MVC Test使用 Spring MVC Test 测试 Spring MVC @ExceptionHandler 方法
【发布时间】:2013-05-16 04:22:03
【问题描述】:

我有以下简单的控制器来捕获任何意外异常:

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

我正在尝试使用 Spring MVC 测试框架编写集成测试。这是我目前所拥有的:

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

我在 Spring MVC 基础架构中注册了 ExceptionController 和一个模拟 StatusController。 在测试方法中,我设置了从 StatusController 抛出异常的期望。

异常被抛出,但 ExceptionController 没有处理它。

我希望能够测试 ExceptionController 是否获得异常并返回适当的响应。

对为什么这不起作用以及我应该如何进行这种测试有什么想法吗?

谢谢。

【问题讨论】:

  • 我猜在测试异常处理程序时没有被分配,不知道确切原因,但这就是它发生的原因,看看这个答案stackoverflow.com/questions/11649036/…
  • 有这方面的消息吗?我也有同样的情况。
  • 我没有找到解决方案。我决定我会相信 @ExceptionHandler 的工作原理,并且由于方法本身很简单,我决定我可以不测试该注释而生活。您仍然可以使用常规单元测试来测试该方法。
  • 可能您的异常扩展了 Throwable 而不是 Exception。我遇到了同样的问题并检查了 InvocableHandlerMethod 中的代码,该代码检查了else if (targetException instanceof Exception) { throw (Exception) targetException; }
  • 检查this 解决方案是否有帮助。将 $.error 替换为 $.message

标签: spring spring-mvc mockito spring-mvc-test


【解决方案1】:

由于您使用的是独立设置测试,您需要手动提供异常处理程序。

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

几天前我也遇到了同样的问题,你可以在这里看到我自己回答的问题和解决方案Spring MVC Controller Exception Test

希望我的回答能帮到你

【讨论】:

    【解决方案2】:

    试试看;

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

    【讨论】:

      【解决方案3】:

      此代码将增加使用异常控制建议的能力。

      @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】:

        我刚刚遇到了同样的问题,以下对我有用:

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

        【讨论】:

        • 虽然这可行,但不得不手动注入所有依赖项是一件很痛苦的事情。 @WebMvcTest注解不应该把ControllerAdvice作为参数吗?
        • @StuartMcIntyre 是的 SpringBootTest/WebMvcTest 应该是首选方法。我很少在绿地项目中使用standaloneSetup(),但如果我没记错的话,我试图添加少量测试,对现有测试套件的影响最小。
        • 愿上帝保佑你以最高的薪水。现在很难找到一个简单直接的答案来回答这里的问题
        【解决方案5】:

        这样更好:

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

        不要忘记扫描 @Configuration 类中的 @ControllerAdvice bean:

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

        ...在 Spring 4.0.2.RELEASE 上测试

        【讨论】:

          【解决方案6】:

          使用 Spring MockMVC 模拟一个 servletContainer,您可以将任何请求过滤或异常处理测试合并到您的单元测试套件中。

          您可以使用以下方法配置此设置:

          给定一个自定义 RecordNotFound 异常...

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

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

          配置定制的测试上下文:设置@ContextConfiguration 来指定测试所需的类。将 Mockito MockMvc 设置为 servlet 容器模拟器,并设置您的测试夹具和依赖项。

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

          配置一个 mock MVC "servlet emulator":在上下文中注册处理程序 bean 并构建 mockMvc 模拟器(注意:有两种可能的配置:standaloneSetup 或 webAppContextSetup;参考documentation) .构建器正确地实现了构建器模式,因此您可以在调用 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");
              }
              ....
          

          获取异常解析器

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

          运行您的测试

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

          希望这会有所帮助。

          杰克。

          【讨论】:

          • 您的代码中哪个包包含 getExceptionResolver() 函数?
          • @Dhana 解决了缺少的解析器吸气剂。感谢升旗。
          猜你喜欢
          • 2013-12-21
          • 1970-01-01
          • 2013-09-03
          • 1970-01-01
          • 2012-07-23
          • 2012-06-15
          • 1970-01-01
          • 1970-01-01
          • 2013-10-06
          相关资源
          最近更新 更多