简体   繁体   English

Spring MVC 控制器单元测试不调用@ControllerAdvice

[英]Spring MVC Controllers Unit Test not calling @ControllerAdvice

I have a set of Controllers in the application and a class annotated as @ControllerAdvice which sets up certain data elements that are used in each of these controllers.我在应用程序中有一组控制器和一个注释为@ControllerAdvice的类,它设置了在每个控制器中使用的某些数据元素。 I'm using Spring MVC 3.2 and have Junits for these controllers.我正在使用Spring MVC 3.2并为这些控制器安装了 Junit。 When I run the Junit the control is not going to the ControllerAdvice class wheres it works fine if I deploy the app in Tomcat and submit a request through browser.当我运行 Junit 时,控件不会转到ControllerAdvice类,如果我在Tomcat部署应用程序并通过浏览器提交请求,它可以正常工作。

Any thoughts please?.请问有什么想法吗?

After using the answer from @eugene-to and another similar one here I found limitations and raised an issue on Spring: https://jira.spring.io/browse/SPR-12751使用答案来自@尤金到和其他类似的一个后,在这里我找到了限制,并在春季提出的一个问题: https://jira.spring.io/browse/SPR-12751

As a result, Spring test introduced the ability to register @ControllerAdvice classes in the builder in 4.2.因此,Spring 测试在 4.2 中引入了在构建器中注册@ControllerAdvice类的功能。 If you are using Spring Boot then you will need 1.3.0 or later.如果您使用的是Spring Boot,那么您将需要 1.3.0 或更高版本。

With this improvement, if you are using standalone setup then you can set one or more ControllerAdvice instances like so:通过这一改进,如果您使用独立设置,那么您可以像这样设置一个或多个ControllerAdvice实例:

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

Note: the name setControllerAdvice() may not make it immediately obvious but you can pass many instances to it, since it has a var-args signature.注意:名称setControllerAdvice()可能不会立即显而易见,但您可以将许多实例传递给它,因为它具有 var-args 签名。

Suppose you have class MyControllerAdvice annotated with @ControllerAdvice that has methods annotated with @ExceptionHandler.假设您有用@ControllerAdvice 注释的MyControllerAdvice 类,该类具有用@ExceptionHandler 注释的方法。 For MockMvc you can easily add this class as exception resolver.对于 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;
}

I had similar problem when trying to test ExceptionHandler annotated with @ControllerAdvice .我在尝试测试用@ControllerAdvice注释的ExceptionHandler时遇到了类似的问题。 In my case I had to add @Configuration file with @EnableWebMvc annotation to @ContextConfiguration on test class.在我的情况下,我必须将带有@EnableWebMvc注释的@Configuration文件添加到测试类的@ContextConfiguration

So my test looked like this:所以我的测试是这样的:

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

With @EnableWebMvc configuration my test passed.使用@EnableWebMvc配置我的测试通过了。

This code is working for me:这段代码对我有用:

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

I've been struggling with the same thing for quite some time.很长一段时间以来,我一直在为同样的事情苦苦挣扎。 After much digging, the best reference was the Spring documentation:经过大量挖掘,最好的参考是 Spring 文档:

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

In short, if you are simply testing a controller and its methods then you can use the 'standaloneSetup' method which creates a simple Spring MVC configuration.简而言之,如果您只是测试控制器及其方法,那么您可以使用“standaloneSetup”方法来创建一个简单的 Spring MVC 配置。 This will not include your error handler that you annotate with @ControllerAdvice.不会包括您使用 @ControllerAdvice 注释的错误处理程序。

private MockMvc mockMvc;

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

// ...

To create a more complete Spring MVC configuration that does contain your error handler you should use the following setup:要创建一个更完整的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;

    // ...

}

@tunguski sample code works but it pays to understand how things work. @tunguski 示例代码有效,但了解事情的工作原理是值得的。 This is just one way to set things up.这只是一种设置方式。

@EnableWebMvc is equivalent to following in a spring configuration file @EnableWebMvc相当于在spring配置文件中跟随

<mvc:annotation-driven />

Essentially for things to work you need to initialize Spring Mvc and load all your controllers and bean references.本质上,为了工作,您需要初始化 Spring Mvc 并加载所有控制器和 bean 引用。 So following could be a valid setup as well as an alternate所以以下可能是一个有效的设置以及替代

Following is how you would setup the test class以下是您将如何设置测试类

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

And following could be the spring configuration for the test以下可能是测试的弹簧配置

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

I encountered this issue while writing controller tests with spock (groovy).我在用spock (groovy) 编写控制器测试时遇到了这个问题。 My test class was originally written like:我的测试类最初是这样写的:

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

This caused ControllerAdvice to be ignored.这导致 ControllerAdvice 被忽略。 Changing the code to to Autowire the mocks fixed the problem.将代码更改为 Autowire 模拟解决了问题。

@AutoConfigureMockMvc(secure = false)
@SpringBootTest
@Category(RestTest)
class FooControllerTest extends Specification {

  @AutowiredMock
  FooService FooService

  @Autowired
  MockMvc mockMvc

The ControllerAdvice should be picked up by @WebMvcTest , see also Spring-Doc Works so far for me. ControllerAdvice应该由@WebMvcTest ,到目前为止,我还可以参阅Spring-Doc Works。

Example:例子:

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

You would need to provide more info, and maybe some actual code and/or config files, before you can expect specific answers.您需要提供更多信息,也许还有一些实际的代码和/或配置文件,然后才能得到具体的答案。 That said, based on the little you have provided, it sounds like the annotated bean is not being loaded.也就是说,根据您提供的一点信息,听起来好像没有加载带注释的 bean。

Try adding the following to your test applicationContext.xml (or equivalent spring config file, if you are using one).尝试将以下内容添加到您的测试 applicationContext.xml(或等效的 spring 配置文件,如果您正在使用)。

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

Alternatively, you may need to 'manually' load the contexts within the tests by including the following annotations before your test class:或者,您可能需要通过在测试类之前包含以下注释来“手动”加载测试中的上下文:

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

Good luck!祝你好运!

I suspect you need to use asyncDispatch in your test;我怀疑您需要在测试中使用 asyncDispatch; the regular testing framework is broken with asynchronous controllers.常规测试框架被异步控制器破坏了。

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

The simplest way it's to add Your @ControllerAdvice annotated class to @ContextConfiguration.最简单的方法是将您的@ControllerAdvice 注释类添加到@ContextConfiguration。

I had to change from this我不得不改变这个

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

to this:对此:

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

I am using Spring Boot 2.x, but it seems MockMvcBuilders is not required anymore or as we are defining the ControllerAdvice as part of the Configuration, it gets loaded.我正在使用 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());
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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