Reputation: 1151
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. I'm using Spring MVC 3.2
and have Junits for these controllers. 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.
Any thoughts please?.
Upvotes: 96
Views: 107930
Reputation: 5147
After using the answer from @eugene-to and another similar one here I found limitations and raised an issue on Spring: https://github.com/spring-projects/spring-framework/issues/17348
As a result, Spring test introduced the ability to register @ControllerAdvice
classes in the builder in 4.2. If you are using Spring Boot then you will need 1.3.0 or later.
With this improvement, if you are using standalone setup then you can set one or more ControllerAdvice
instances like so:
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.
Upvotes: 151
Reputation: 543
When using @WebMvcTest with specific controllers, controller advice will not be used by the spring configuration: https://github.com/spring-projects/spring-boot/issues/12979.
You can explicitly tell the spring, via the @Import annotation, to use the controller advice:
@WebMvcTest(controllers = AppController.class)
@Import(AppControllerAdvice.class)
class AppControllerTest {
@Autowired
private MockMvc mockMvc;
}
Upvotes: 2
Reputation: 54
Just had the same issue, but solved it by adding the advicer to the classes in the @SpringBootTest:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {MyController.class, MyControllerAdvice.class})
@AutoConfigureMockMvc(secure = false)
@ContextConfiguration(classes = {MyTestConfig.class})
@EnableWebMvc
Upvotes: -1
Reputation: 763
I had similar problem when trying to test ExceptionHandler
annotated with @ControllerAdvice
. In my case I had to add @Configuration
file with @EnableWebMvc
annotation to @ContextConfiguration
on test class.
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.
Upvotes: 27
Reputation: 8383
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));
}
}
Upvotes: 21
Reputation: 2049
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.
@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 = "[email protected]", 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());
}
}
Upvotes: 0
Reputation: 41
The simplest way it's to add Your @ControllerAdvice annotated class to @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
Upvotes: 1
Reputation: 5823
The ControllerAdvice
should be picked up by @WebMvcTest
, see also Spring-Doc Works so far for me.
Example:
@RunWith(SpringRunner.class)
@WebMvcTest(ProductViewController.class)
Upvotes: 4
Reputation: 1812
I suspect you need to use asyncDispatch in your test; 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
Upvotes: 0
Reputation: 6154
I encountered this issue while writing controller tests with 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. Changing the code to to Autowire the mocks fixed the problem.
@AutoConfigureMockMvc(secure = false)
@SpringBootTest
@Category(RestTest)
class FooControllerTest extends Specification {
@AutowiredMock
FooService FooService
@Autowired
MockMvc mockMvc
Upvotes: 1
Reputation: 2205
@tunguski sample code works but it pays to understand how things work. This is just one way to set things up.
@EnableWebMvc
is equivalent to following in a spring configuration file
<mvc:annotation-driven />
Essentially for things to work you need to initialize Spring Mvc and load all your controllers and bean references. 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" />
Upvotes: 1
Reputation: 1938
Suppose you have class MyControllerAdvice annotated with @ControllerAdvice that has methods annotated with @ExceptionHandler. For MockMvc you can easily add this class as exception resolver.
@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;
}
Upvotes: 42
Reputation: 71
I've been struggling with the same thing for quite some time. After much digging, the best reference was the Spring documentation:
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. This will not include your error handler that you annotate with @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:
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration("test-servlet-context.xml")
public class AccountTests {
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
@Autowired
private AccountService accountService;
// ...
}
Upvotes: 7
Reputation: 1041
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.
Try adding the following to your test applicationContext.xml (or equivalent spring config file, if you are using one).
<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!
Upvotes: 0