【问题标题】:Spring MVC Controllers Unit Test not calling @ControllerAdviceSpring MVC 控制器单元测试未调用 @ControllerAdvice
【发布时间】:2013-03-08 19:57:22
【问题描述】:

我在应用程序中有一组控制器和一个注释为@ControllerAdvice 的类,它设置了在每个控制器中使用的某些数据元素。我正在使用Spring MVC 3.2 并为这些控制器配备了 Junits。当我运行 Junit 时,控件不会转到 ControllerAdvice 类,如果我在 Tomcat 中部署应用程序并通过浏览器提交请求,它可以正常工作。

有什么想法吗?

【问题讨论】:

  • 感谢更新。我面临着类似的问题,其中@ControllerAdvice 类中的@ExceptionHandler(Exception.class) 注释方法未通过单元测试-webAppContextSetup(wac).build() 调用。部署为 webapp 时调用 ExceptionHandler 注解的方法。
  • 对这个问题的正确响应随着时间的推移而改变。请参阅@Matt Byrne 的最后回答:stackoverflow.com/a/28727831/2288986
  • WebTestClient 调用也发生了同样的问题。有什么解决办法吗?

标签: spring spring-mvc junit


【解决方案1】:

使用@eugene-to 和另一个类似here 的答案后,我发现了限制并在Spring 上提出了一个问题:https://jira.spring.io/browse/SPR-12751

因此,Spring 测试在 4.2 中引入了在构建器中注册 @ControllerAdvice 类的能力。如果您使用的是 Spring Boot,那么您将需要 1.3.0 或更高版本。

通过这项改进,如果您使用独立设置,则可以像这样设置一个或多个 ControllerAdvice 实例:

mockMvc = MockMvcBuilders.standaloneSetup(yourController)
            .setControllerAdvice(new YourControllerAdvice())
            .build();

注意:名称setControllerAdvice() 可能不会立即显示出来,但您可以向它传递许多实例,因为它具有 var-args 签名。

【讨论】:

  • 我为那些被弹簧验证器惹恼的人添加了一些东西,就像我一样:this.mockMvc = MockMvcBuilders.standaloneSetup(yourController) .setValidator(mock(Validator.class))。 setControllerAdvice(new CaissierExceptionHandler()) .build();
  • 如果您使用的是 webAppContextSetup 而不是standaloneSetup,您可能忘记了@EnableWebMvc 注解。请参阅下面 tunguski stackoverflow.com/a/18911362/5570214 的答案。
  • 当我在 Spring 4.2.X 版本中使用standaloneSetup 时,setContollerAdvice 不起作用。
  • 如果建议适用于所有控制器怎么办?是不是要一个一个手动注册?
  • WebTestClient 调用也发生了同样的问题。有什么解决办法吗?
【解决方案2】:

假设你有一个用@ControllerAdvice注解的类MyControllerAdvice,它的方法用@ExceptionHandler注解。对于 MockMvc,您可以轻松地将此类添加为异常解析器。

@Before
public void beforeTest() {
    MockMvc mockMvc = standaloneSetup(myControllers)
        .setHandlerExceptionResolvers(createExceptionResolver())
        .build();
}

private ExceptionHandlerExceptionResolver createExceptionResolver() {
    ExceptionHandlerExceptionResolver exceptionResolver = new ExceptionHandlerExceptionResolver() {
        protected ServletInvocableHandlerMethod getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) {
            Method method = new ExceptionHandlerMethodResolver(MyControllerAdvice.class).resolveMethod(exception);
            return new ServletInvocableHandlerMethod(new MyControllerAdvice(), method);
        }
    };
    exceptionResolver.afterPropertiesSet();
    return exceptionResolver;
}

【讨论】:

  • 这对我使用异步请求处理不起作用
  • 很好的答案,但是如果您的解析器返回任何类型的对象,您还需要为您的exceptionResolver 设置MessageConverters。请参阅此答案以获取示例:stackoverflow.com/a/25881974/945789
【解决方案3】:

我在尝试测试带有@ControllerAdvice 注释的ExceptionHandler 时遇到了类似的问题。在我的情况下,我必须在测试类上将带有@EnableWebMvc 注释的@Configuration 文件添加到@ContextConfiguration。

所以我的测试看起来像这样:

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes = {
  RestProcessingExceptionHandler.class,
  TestConfiguration.class,
  RestProcessingExceptionThrowingController.class })
public class TestRestProcessingExceptionHandler {

  private MockMvc mockMvc;
  @Autowired
  WebApplicationContext wac;

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

  @Configuration
  // !!! this is very important - conf with this annotation 
  //     must be included in @ContextConfiguration
  @EnableWebMvc
  public static class TestConfiguration { }

  @Controller
  @RequestMapping("/tests")
  public static class RestProcessingExceptionThrowingController {
    @RequestMapping(value = "/exception", method = GET)
    public @ResponseBody String find() {
      throw new RestProcessingException("global_error_test");
    }
  }

  @Test
  public void testHandleException() throws Exception {
    mockMvc.perform(get("/tests/exception"))
      .andExpect(new ResultMatcher() {
        @Override
        public void match(MvcResult result) throws Exception {
          result.getResponse().getContentAsString().contains("global_error_test");
        }
      })
      .andExpect(status().isBadRequest());
  }
}

使用@EnableWebMvc 配置,我的测试通过了。

【讨论】:

  • java.lang.IllegalStateException: 无法加载 ApplicationContext :(
  • 这对我有用,但我能够将 @EnableWebMvc 添加到测试类本身而不是包含 TestConfiguration 类,即:@WebAppConfiguration @ContextConfiguration ... @EnableWebMvc public class TestRestProcessingExceptionHandler {
【解决方案4】:

这段代码对我有用:

public class MyGlobalExceptionHandlerTest {

    private MockMvc mockMvc;

    @Mock
    HealthController healthController;

    @BeforeTest
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        mockMvc = MockMvcBuilders.standaloneSetup(healthController)
            .setControllerAdvice(new GlobalExceptionHandler())
            .build();
    }

    @Test(groups = { "services" })
    public void testGlobalExceptionHandlerError() throws Exception {
        Mockito.when(healthController.health())]
               .thenThrow(new RuntimeException("Unexpected Exception"));
        mockMvc.perform(get("/health")).andExpect(status().is(500));
    }
}

【讨论】:

  • 这适用于哪个版本的弹簧靴?使用 1.5.6 时,此代码从不调用我的 controlleradvice
  • @Bikesh,它对我有用,非常感谢,你节省了我的时间。
【解决方案5】:

很长一段时间以来,我一直在为同样的事情苦苦挣扎。经过大量挖掘,最好的参考是 Spring 文档:

http://static.springsource.org/spring/docs/3.2.x/spring-framework-reference/html/testing.html#spring-mvc-test-framework

简而言之,如果您只是测试控制器及其方法,那么您可以使用创建简单 Spring MVC 配置的 'standaloneSetup' 方法。这将不包含您使用 @ControllerAdvice 注释的错误处理程序。

private MockMvc mockMvc;

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

// ...

要创建一个确实包含错误处理程序的更完整的 Spring MVC 配置,您应该使用以下设置:

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration("test-servlet-context.xml")
public class AccountTests {

    @Autowired
    private WebApplicationContext wac;

    private MockMvc mockMvc;

    @Autowired
    private AccountService accountService;

    // ...

}

【讨论】:

  • 你还需要初始化mockMvc,上面例子中没有显示:@Before public void setUp() { mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build(); }
  • 你可以使用 .setControllerAdvice(xxx) 和 StandaloneMockMvcBuilder 来注入你的 ControllerAdvice bean
  • @frno 救了我的命。这是最简单的解决方案。 mockMvc = MockMvcBuilders.standaloneSetup(controller).setControllerAdvice(new MyAppExceptionHandler()).build();
【解决方案6】:

ControllerAdvice 应由@WebMvcTest 领取,另请参阅Spring-Doc 到目前为止为我工作。

例子:

@RunWith(SpringRunner.class)
@WebMvcTest(ProductViewController.class)

【讨论】:

  • 这对我来说适用于 spring-boot 2.3 和 Junit 5。甚至不需要 @RunWith(SpringRunner.class),
  • 它确实适用于JUnit-5,但在我的情况下不在参数化测试中......无法弄清楚为什么......@smile任何想法为什么?你试过吗?
  • @my- 需要查看代码。可能你可以用代码和完整的细节提出一个新问题吗?
【解决方案7】:

@tunguski 示例代码可以工作,但了解事情的工作原理是值得的。这只是一种设置方式。

@EnableWebMvc 相当于在一个spring配置文件中跟随

<mvc:annotation-driven />

为了使事情正常工作,您需要初始化 Spring Mvc 并加载所有控制器和 bean 引用。所以跟随可能是一个有效的设置以及替代

以下是设置测试类的方法

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations = { "classpath: "classpath:test-context.xml" })
    @WebAppConfiguration    
    public class BaseTest {

        @Autowired
        WebApplicationContext wac;

        private MockMvc mockMvc;

        @Before
        public void setUp()  {
            mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
        }
    }

以下可能是测试的弹簧配置

<mvc:annotation-driven />
<context:component-scan base-package="com.base.package.controllers" />

【讨论】:

    【解决方案8】:

    我在使用 spock (groovy) 编写控制器测试时遇到了这个问题。我的测试类最初是这样写的:

    @AutoConfigureMockMvc(secure = false)
    @SpringBootTest
    @Category(RestTest)
    class FooControllerTest extends Specification {
      def fooService = Mock(FooService)
      def underTest = new FooController(FooService)
      def mockMvc = MockMvcBuilders.standaloneSetup(underTest).build()
    ....
    }
    

    这导致 ControllerAdvice 被忽略。将代码更改为 Autowire the mocks 解决了这个问题。

    @AutoConfigureMockMvc(secure = false)
    @SpringBootTest
    @Category(RestTest)
    class FooControllerTest extends Specification {
    
      @AutowiredMock
      FooService FooService
    
      @Autowired
      MockMvc mockMvc
    

    【讨论】:

      【解决方案9】:

      您需要提供更多信息,可能还需要提供一些实际代码和/或配置文件,然后才能获得具体答案。也就是说,根据您提供的信息,听起来带注释的 bean 没有被加载。

      尝试将以下内容添加到您的测试 applicationContext.xml(或等效的 spring 配置文件,如果您正在使用)。

      <context:component-scan base-package="com.example.path.to.package" />
      

      或者,您可能需要通过在测试类之前添加以下注释来“手动”加载测试中的上下文:

      @RunWith(SpringJUnit4ClassRunner.class)
      @ContextConfiguration("/applicationContext.xml")
      

      祝你好运!

      【讨论】:

        【解决方案10】:

        我怀疑您需要在测试中使用 asyncDispatch;异步控制器破坏了常规测试框架。

        尝试以下方法:https://github.com/spring-projects/spring-framework/blob/master/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/AsyncTests.java

        【讨论】:

          【解决方案11】:

          最简单的方法是将您的@ControllerAdvice 注释类添加到@ContextConfiguration。

          我不得不改变

          @AutoConfigureMockMvc
          @ContextConfiguration(classes = OrderController.class)
          @WebMvcTest
          class OrdersIntegrationTest
          

          到这里:

          @AutoConfigureMockMvc
          @ContextConfiguration(classes = {OrderController.class, OrdersExceptionHandler.class})
          @WebMvcTest
          class OrdersIntegrationTest
          

          【讨论】:

            【解决方案12】:

            我使用的是 Spring Boot 2.x,但似乎不再需要 MockMvcBuilders,或者当我们将 ControllerAdvice 定义为配置的一部分时,它会被加载。

            @WebMvcTest
            @ContextConfiguration(classes = {
              UserEndpoint.class, //the controller class for test
              WebConfiguration.class, //security configurations, if any
              StandardRestExceptionInterpreter.class. //<-- this is the ControllerAdvice class
            })
            @WithMockUser(username = "test@asdf.com", authorities = {"DEFAULT"})
            @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
            public class UserEndpointTests {
            
            @Test
            @Order(3)
            public void shouldThrowExceptionWhenRegisteringDuplicateUser() throws Exception {
                //do setup...
                Mockito.doThrow(EntityExistsException.class).when(this.userService).register(user);
            
                this.mockMvc
                        .perform(MockMvcRequestBuilders
                                .post("/users")
                                .contentType(MediaType.APPLICATION_JSON)
                                .content(this.objectMapper.writeValueAsString(user)))
                        .andDo(MockMvcResultHandlers.print())
                        .andExpect(MockMvcResultMatchers.status().isConflict());
                }
            }
            

            【讨论】:

              【解决方案13】:

              刚遇到同样的问题,但通过将建议器添加到@SpringBootTest 中的类来解决它:

              @RunWith(SpringRunner.class)
              @SpringBootTest(classes = {MyController.class, MyControllerAdvice.class})
              @AutoConfigureMockMvc(secure = false)
              @ContextConfiguration(classes = {MyTestConfig.class})
              @EnableWebMvc
              

              【讨论】:

                猜你喜欢
                • 2017-12-06
                • 2015-05-14
                • 1970-01-01
                • 2016-01-31
                • 2011-12-01
                • 1970-01-01
                • 1970-01-01
                • 2023-03-15
                相关资源
                最近更新 更多