I have a simple annotated controller similar to this one:
#Controller
public class MyController {
#RequestMapping("/{id}.html")
public String doSomething(#PathVariable String id, Model model) {
// do something
return "view";
}
}
and I want to test it with an unit test like this:
public class MyControllerTest {
#Test
public void test() {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setRequestURI("/test.html");
new AnnotationMethodHandlerAdapter()
.handle(request, new MockHttpServletResponse(), new MyController());
// assert something
}
}
The problem is that AnnotationMethodHandlerAdapter.handler() method throws an exception:
java.lang.IllegalStateException: Could not find #PathVariable [id] in #RequestMapping
at org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter$ServletHandlerMethodInvoker.resolvePathVariable(AnnotationMethodHandlerAdapter.java:642)
at org.springframework.web.bind.annotation.support.HandlerMethodInvoker.resolvePathVariable(HandlerMethodInvoker.java:514)
at org.springframework.web.bind.annotation.support.HandlerMethodInvoker.resolveHandlerArguments(HandlerMethodInvoker.java:262)
at org.springframework.web.bind.annotation.support.HandlerMethodInvoker.invokeHandlerMethod(HandlerMethodInvoker.java:146)
I'd call what you're after an integration test based on the terminology in the Spring reference manual. How about doing something like:
import static org.springframework.test.web.ModelAndViewAssert.*;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({/* include live config here
e.g. "file:web/WEB-INF/application-context.xml",
"file:web/WEB-INF/dispatcher-servlet.xml" */})
public class MyControllerIntegrationTest {
#Inject
private ApplicationContext applicationContext;
private MockHttpServletRequest request;
private MockHttpServletResponse response;
private HandlerAdapter handlerAdapter;
private MyController controller;
#Before
public void setUp() {
request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
handlerAdapter = applicationContext.getBean(HandlerAdapter.class);
// I could get the controller from the context here
controller = new MyController();
}
#Test
public void testDoSomething() throws Exception {
request.setRequestURI("/test.html");
final ModelAndView mav = handlerAdapter.handle(request, response,
controller);
assertViewName(mav, "view");
// assert something
}
}
For more information I've written a blog entry about integration testing Spring MVC annotations.
As of Spring 3.2, there is a proper way to test this, in an elegant and easy way. You will be able to do things like this:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration("servlet-context.xml")
public class SampleTests {
#Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
#Before
public void setup() {
this.mockMvc = webAppContextSetup(this.wac).build();
}
#Test
public void getFoo() throws Exception {
this.mockMvc.perform(get("/foo").accept("application/json"))
.andExpect(status().isOk())
.andExpect(content().mimeType("application/json"))
.andExpect(jsonPath("$.name").value("Lee"));
}
}
For further information, take a look at http://blog.springsource.org/2012/11/12/spring-framework-3-2-rc1-spring-mvc-test-framework/
A promising framework for testing Spring MVC
https://github.com/SpringSource/spring-test-mvc
The exception message refers to a "feed" variable, which isn't present in your sample code, it's likely being caused by something you haven't shown us.
Also, your test is testing Spring and your own code. Is this really what you want to do?
It's better to assume that Spring works (which it does), and just test your own class, i.e. call MyController.doSomething() directly. That's one benefit of the annotation approach - you don't need to use mock requests and responses, you just use domain POJOs.
I've found that you can manually insert a PathVariable mapping into the request object. This is distinctly non-ideal but appears to work. In your example, something like:
#Test
public void test() {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setRequestURI("/test.html");
HashMap<String, String> pathvars = new HashMap<String, String>();
pathvars.put("id", "test");
request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, pathvars);
new AnnotationMethodHandlerAdapter().handle(request, new MockHttpServletResponse(), new MyController());
// assert something
}
I'd definitely be interested in finding a better option.
Provided you are using Spring 3.0.x.
Here I suggest a merger of Emil and scarba05 answers using spring-test not spring-test-mvc. Please skip this answer and refer to spring-test-mvc examples if you are using Spring 3.2.x or later
MyControllerWithParameter.java
#Controller
public class MyControllerWithParameter {
#RequestMapping("/testUrl/{pathVar}/some.html")
public String passOnePathVar(#PathVariable String pathVar, ModelMap model){
model.addAttribute("SomeModelAttribute",pathVar);
return "viewName";
}
}
MyControllerTest.java
import static org.springframework.test.web.ModelAndViewAssert.assertViewName;
import java.util.HashMap;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.web.ModelAndViewAssert;
import org.springframework.web.servlet.HandlerAdapter;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations =
{"file:src\\main\\webapp\\WEB-INF\\spring\\services\\servlet-context.xml"
})
public class MyControllerTest {
private MockHttpServletRequest request;
private MockHttpServletResponse response;
private HandlerAdapter handlerAdapter;
#Before
public void setUp() throws Exception {
request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
this.handlerAdapter = applicationContext.getBean(AnnotationMethodHandlerAdapter.class);
}
// Container beans
private MyControllerWithParameter myController;
private ApplicationContext applicationContext;
public ApplicationContext getApplicationContext() {
return applicationContext;
}
#Autowired
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
public MyControllerWithParameter getMyController() {
return myController;
}
#Autowired
public void setMyController(MyControllerWithParameter myController) {
this.myController = myController;
}
#Test
public void test() throws Exception {
request.setRequestURI("/testUrl/Irrelavant_Value/some.html");
HashMap<String, String> pathvars = new HashMap<String, String>();
// Populate the pathVariable-value pair in a local map
pathvars.put("pathVar", "Path_Var_Value");
// Assign the local map to the request attribute concerned with the handler mapping
request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, pathvars);
final ModelAndView modelAndView = this.handlerAdapter.handle(request, response, myController);
ModelAndViewAssert.assertAndReturnModelAttributeOfType(modelAndView, "SomeModelAttribute", String.class);
ModelAndViewAssert.assertModelAttributeValue(modelAndView, "SomeModelAttribute", "Path_Var_Value");
ModelAndViewAssert.assertViewName(modelAndView, "viewName");
}
}
I'm not sure my original answer is going to help with #PathVariable. I've just tried testing an #PathVariable and I get the following exception:
org.springframework.web.bind.annotation.support.HandlerMethodInvocationException: Failed to invoke handler method [public org.springframework.web.servlet.ModelAndView test.MyClass.myMethod(test.SomeType)]; nested exception is java.lang.IllegalStateException: Could not find #PathVariable [parameterName] in #RequestMapping
The reason is that the path variables in the request get parsed by an interceptor. The following approach works for me:
import static org.springframework.test.web.ModelAndViewAssert.*;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({"file:web/WEB-INF/application-context.xml",
"file:web/WEB-INF/dispatcher-servlet.xml"})
public class MyControllerIntegrationTest {
#Inject
private ApplicationContext applicationContext;
private MockHttpServletRequest request;
private MockHttpServletResponse response;
private HandlerAdapter handlerAdapter;
#Before
public void setUp() throws Exception {
this.request = new MockHttpServletRequest();
this.response = new MockHttpServletResponse();
this.handlerAdapter = applicationContext.getBean(HandlerAdapter.class);
}
ModelAndView handle(HttpServletRequest request, HttpServletResponse response)
throws Exception {
final HandlerMapping handlerMapping = applicationContext.getBean(HandlerMapping.class);
final HandlerExecutionChain handler = handlerMapping.getHandler(request);
assertNotNull("No handler found for request, check you request mapping", handler);
final Object controller = handler.getHandler();
// if you want to override any injected attributes do it here
final HandlerInterceptor[] interceptors =
handlerMapping.getHandler(request).getInterceptors();
for (HandlerInterceptor interceptor : interceptors) {
final boolean carryOn = interceptor.preHandle(request, response, controller);
if (!carryOn) {
return null;
}
}
final ModelAndView mav = handlerAdapter.handle(request, response, controller);
return mav;
}
#Test
public void testDoSomething() throws Exception {
request.setRequestURI("/test.html");
request.setMethod("GET");
final ModelAndView mav = handle(request, response);
assertViewName(mav, "view");
// assert something else
}
I've add a new blog post on integration testing spring mvc annotations
Related
we are trying to do an intergration test our interceptors in our spring boot application using spring boot version 1.4.0, but not sure how; here is our application setting
#Configuration
#EnableAutoConfiguration()
#ComponentScan
public class Application extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilderconfigure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
we then customed out webmvc by extending WebMvcConfigurerAdapter
#Configuration
public class CustomServletContext extends WebMvcConfigurerAdapter{
#Override
public void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(testInterceptor).addPathPatterns("/testapi/**");
}
}
so we wanna to test the interceptor, but we don't wanna really start the application, cause there are many dependency beans that need to read a externally defined property files to construct
we have tried the following
#SpringBootTest(classes = CustomServletContext.class)
#RunWith(SpringRunner.class)
public class CustomServletContextTest {
#Autowired
private ApplicationContext applicationContext;
#Test
public void interceptor_request_all() throws Exception {
RequestMappingHandlerMapping mapping = (RequestMappingHandlerMapping) applicationContext
.getBean("requestMappingHandlerMapping");
assertNotNull(mapping);
MockHttpServletRequest request = new MockHttpServletRequest("GET",
"/test");
HandlerExecutionChain chain = mapping.getHandler(request);
Optional<TestInterceptor> containsHandler = FluentIterable
.from(Arrays.asList(chain.getInterceptors()))
.filter(TestInterceptor.class).first();
assertTrue(containsHandler.isPresent());
}
}
but it alters org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'requestMappingHandlerMapping' is defined
Do we need to create a bean of requestMappingHandlerMapping to test the interceptors? is there any magical way to do this in spring boot ?
You can create a test like this :
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = { MyIncludedConfig.class })
#ActiveProfiles("my_enabled_profile")
public class BfmSecurityInterceptorTest2 {
public static final String TEST_URI = "/test";
public static final String RESPONSE = "test";
// this way you can provide any beans missing due to limiting the application configuration scope
#MockBean
private DataSource dataSource;
#Autowired
private TestRestTemplate testRestTemplate;
#Test
public void testInterceptor_Session_cookie_present_Authorized() throws Exception {
ResponseEntity<String> responseEntity = testRestTemplate.getForEntity(TEST_URI, String.class);
assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(responseEntity.getBody()).isEqualTo(RESPONSE);
}
#SpringBootApplication
#RestController
public static class TestApplication {
#GetMapping(TEST_URI)
public String test() {
return RESPONSE;
}
}
}
Notes
Interceptors only work if you set SpringBootTest.WebEnvironment.RANDOM_PORT
You have to provide enough configuration so your interceptors are executed
To speed up the test you can exclude not wanted beans and configurations, see examples
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.assertTrue;
#SpringBootTest()
class LoggingInterceptorConfigurationTest {
#Autowired
private RequestMappingHandlerMapping mapping;
#Test
public void LoggingInterceptorShouldBeApplied() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/api/example");
HandlerExecutionChain chain = mapping.getHandler(request);
assert chain != null;
Optional<HandlerInterceptor> LoggingInterceptor = chain.getInterceptorList()
.stream()
.filter(LoggingInterceptor.class::isInstance)
.findFirst();
assertTrue(LoggingInterceptor.isPresent());
}
#Test
public void LoggingInterceptorShouldNotBeAppliedToHealthURL() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/health");
HandlerExecutionChain chain = mapping.getHandler(request);
assert chain != null;
Optional<HandlerInterceptor> LoggingInterceptor = chain.getInterceptorList()
.stream()
.filter(LoggingInterceptor.class::isInstance)
.findFirst();
assertTrue(LoggingInterceptor.isEmpty());
}
}
I have following controller code for which I have to write JUnit test case.
public class EquipmentController {
private Map<String, Equipment> equiList = new HashMap <String,Equipment>();
#RequestMapping("/rest/equipment/{Number}")
public Equipment getEquipment(#PathVariable String Number){
if(!equiList.containsKey(Number)){
lNumber = DEFAULT;
}
return equiList.get(Number);
}
}
I'm writing the JUnit test case for the same as below:
import static org.springframework.test.web.ModelAndViewAssert.*;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({/* include live config here
e.g. "file:web/WEB-INF/application-context.xml",
"file:web/WEB-INF/dispatcher-servlet.xml" */})
public class EquipmentControllerTest {
#Inject
private ApplicationContext applicationContext;
private MockHttpServletRequest request;
private MockHttpServletResponse response;
private HandlerAdapter handlerAdapter;
private EquipmentController controller;
#Before
public void setUp() {
request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
handlerAdapter = applicationContext.getBean(HandlerAdapter.class);
// Get the controller from the context here
controller = new EquipmentController();
}
#Test
public void testgetEquipment() throws Exception {
request.getUriString()("lNumber");
final Equipment equip = handlerAdapter.handle(request, response,
controller);
assertViewName(equip, "view");
}
}
But am not sure if this test class is correct or not as I am new to JUnit.
Can anyone please suggest how to do this.
Create a mock of your controller and use MockMvc to test your methods:
import static org.springframework.test.web.ModelAndViewAssert.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({/* include live config here
e.g. "file:web/WEB-INF/application-context.xml",
"file:web/WEB-INF/dispatcher-servlet.xml" */})
public class EquipmentControllerTest {
private MockMvc mockMvc;
private EquipmentController controller;
#Before
public void setUp() {
this.mockMvc = MockMvcBuilders.standaloneSetup(equipmentController).build()
}
#Test
public void testgetEquipment() throws Exception {
this.mockMvc.perform(get("/rest/equipment/{Number}", 3))
.andExpect(status().isOk())
}
}
where "3" represents value of your path variable.
Started studying Spring Test MVC Framework at the office today, it looks handy, but facing some serious trouble right off the bat. Spent a few hours googling, but couldn't find anything related to my issue.
Here's my very simple test class:
import static org.hamcrest.Matchers.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.context.WebApplicationContext;
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(classes = WebAppContext.class)
public class ControllerTests {
#Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
#Before
public void setup() {
mockMvc = webAppContextSetup(wac).build();
}
#Test
public void processFetchErrands() throws Exception {
mockMvc.perform(post("/errands.do?fetchErrands=true"))
.andExpect(status().isOk())
.andExpect(model().attribute("errandsModel", allOf(
hasProperty("errandsFetched", is(true)),
hasProperty("showReminder", is(false)))));
}
}
The test reaches the following controller, but fails on first if clause thanks to not being authorized properly.
#RequestMapping(method = RequestMethod.POST, params="fetchErrands")
public String processHaeAsioinnit(HttpSession session, HttpServletRequest request, ModelMap modelMap,
#ModelAttribute(ATTR_NAME_MODEL) #Valid ErrandsModel model,
BindingResult result, JopoContext ctx) {
if (request.isUserInRole(Authority.ERRANDS.getCode())) {
return Page.NO_AUTHORITY.getCode();
}
[...]
}
How do I add a user role for the MockHttpServletRequest that gets created by MockMvcRequestBuilders.post(), so that I can get past the authority check on my controller?
I know MockHttpServletRequest has a method addUserRole(String role), but since MockMvcRequestBuilders.post() returns a MockHttpServletRequestBuilder, I never get my hands on the MockHttpServletRequest and thus cannot call that method.
Checking the Spring source, MockHttpServletRequestBuilder has no methods related to user roles, nor is the MockHttpServletRequest.addUserRole(String role) ever called in that class, so I have no idea how to tell it to add a user role into the request.
All I can think of is adding a custom filter to filter chain and calling a custom HttpServletRequestWrapper from there that provides an implementation of isUserInRole(), but that seems a little extreme for such a case. Surely the framework should offer something more practical?
I think I found an easier way
#Test
#WithMockUser(username = "username", roles={"ADMIN"})
public void testGetMarkupAgent() throws Exception {
mockMvc.perform(get("/myurl"))
.andExpect([...]);
}
You might need the following maven entry
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<version>4.0.4.RELEASE</version>
<scope>test</scope>
</dependency>
Spring MVC Test has the principal() method that allows to mock the request credentials for cases like this. This is an example of a test where some mock credentials are set:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration("classpath:spring/mvc-dispatcher-servlet.xml")
public class MobileGatewayControllerTest {
private MockMvc mockMvc;
#Autowired
private WebApplicationContext wac;
#Autowired
private Principal principal;
#Autowired
private MockServletContext servletContext;
#Before
public void init() {
mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
#Test
public void testExampleRequest() throws Exception {
servletContext.declareRoles("ROLE_1");
mockMvc.perform(get("/testjunit")
.accept(MediaType.APPLICATION_JSON)
.principal(principal))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().contentType("application/json"))
.andExpect(jsonPath("$.[1]").value("test"));
}
}
and this is an example of how to create a mock principal:
#Configuration
public class SetupTestConfig {
#Bean
public Principal createMockPrincipal() {
Principal principal = Mockito.mock(Principal.class);
Mockito.when(principal.getName()).thenReturn("admin");
return principal;
}
}
You can inject a RequestPostProcessor to configure the MockHttpServletRequest before it's used in the request.
MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.get("/")
.with(new RoleRequestPostProcessor("some role"));
class RoleRequestPostProcessor implements RequestPostProcessor {
private final String role;
public RoleRequestPostProcessor(final String role) {
this.role = role;
}
#Override
public MockHttpServletRequest postProcessRequest(final MockHttpServletRequest request) {
request.addUserRole(role);
return request;
}
}
There is also an easy, alternative way of setting up specific user role for a single request. It might be handy if you want to perform only a single operation as an authorized user (like set up the test fixture), and then check if user with different role can perform certain operation:
ResultActions registerNewUserAsAdmin(String username, String password) throws Exception {
final SignUpRequest signUpPayload = new SignUpRequest(username, password);
final MockHttpServletRequestBuilder registerUserRequest = post(SIGN_UP_URL)
.with(user("admin").roles("ADMIN"))
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(jsonMapper.writeValueAsString(signUpPayload));
return mockMvc.perform(registerUserRequest);
}
See SecurityMockMvcRequestPostProcessors for more details.
IF you are using Authority instead of Roles, then grant the authority in the request like below.
mockMvc.perform(
put(REQUEST_URL).param(PARM, VALUE)
.with(SecurityMockMvcRequestPostProcessors.user(USERNAME).authorities(new SimpleGrantedAuthority("ADMIN")))
.contentType(APPLICATION_FORM_URLENCODED)
)
I am following a Spring 2.5 tutorial and trying, at the same time, updating the code/setup to Spring 3.0.
In Spring 2.5 I had the HelloController (for reference):
public class HelloController implements Controller {
protected final Log logger = LogFactory.getLog(getClass());
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
logger.info("Returning hello view");
return new ModelAndView("hello.jsp");
}
}
And a JUnit test for the HelloController (for reference):
public class HelloControllerTests extends TestCase {
public void testHandleRequestView() throws Exception{
HelloController controller = new HelloController();
ModelAndView modelAndView = controller.handleRequest(null, null);
assertEquals("hello", modelAndView.getViewName());
}
}
But now I updated the controller to Spring 3.0, and it now uses annotations (I also added a message):
#Controller
public class HelloController {
protected final Log logger = LogFactory.getLog(getClass());
#RequestMapping("/hello")
public ModelAndView handleRequest() {
logger.info("Returning hello view");
return new ModelAndView("hello", "message", "THIS IS A MESSAGE");
}
}
Knowing that I am using JUnit 4.9, can some one explain me how to unit test this last controller?
One advantage of annotation-based Spring MVC is that they can be tested in a straightforward manner, like so:
import org.junit.Test;
import org.junit.Assert;
import org.springframework.web.servlet.ModelAndView;
public class HelloControllerTest {
#Test
public void testHelloController() {
HelloController c= new HelloController();
ModelAndView mav= c.handleRequest();
Assert.assertEquals("hello", mav.getViewName());
...
}
}
Is there any problem with this approach?
For more advanced integration testing, there is a reference in Spring documentation to the org.springframework.mock.web.
With mvc:annotation-driven you have to have 2 steps: first you resolve the request to handler using HandlerMapping, then you can execute the method using that handler via HandlerAdapter. Something like:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration("yourContext.xml")
public class ControllerTest {
#Autowired
private RequestMappingHandlerAdapter handlerAdapter;
#Autowired
private RequestMappingHandlerMapping handlerMapping;
#Test
public void testController() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
// request init here
MockHttpServletResponse response = new MockHttpServletResponse();
Object handler = handlerMapping.getHandler(request).getHandler();
ModelAndView modelAndView = handlerAdapter.handle(request, response, handler);
// modelAndView and/or response asserts here
}
}
This works with Spring 3.1, but I guess some variant of this must exist for every version. Looking at the Spring 3.0 code, I'd say DefaultAnnotationHandlerMapping and AnnotationMethodHandlerAdapter should do the trick.
You can also look into other web testing frameworks that are independent of Spring like HtmlUnit, or Selenium. You won't find any more robust strategy with JUnit alone other than what Sasha has described, except you should definitely assert the model.
I ran into a problem the other day where a #Valid annotation was accidentally removed from a controller class. Unfortunately, it didn't break any of our tests. None of our unit tests actually exercise the Spring AnnotationMethodHandlerAdapter pathway. We just test our controller classes directly.
How can I write a unit or integration test that will correctly fail if my #MVC annotations are wrong? Is there a way I can ask Spring to find and exercise the relevant controller with a MockHttpServlet or something?
I write integration tests for this kind of thing. Say you have a bean with validation annotations:
public class MyForm {
#NotNull
private Long myNumber;
...
}
and a controller that handles the submission
#Controller
#RequestMapping("/simple-form")
public class MyController {
private final static String FORM_VIEW = null;
#RequestMapping(method = RequestMethod.POST)
public String processFormSubmission(#Valid MyForm myForm,
BindingResult result) {
if (result.hasErrors()) {
return FORM_VIEW;
}
// process the form
return "success-view";
}
}
and you want to test that the #Valid and #NotNull annotations are wired correctly:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({"file:web/WEB-INF/application-context.xml",
"file:web/WEB-INF/dispatcher-servlet.xml"})
public class MyControllerIntegrationTest {
#Inject
private ApplicationContext applicationContext;
private MockHttpServletRequest request;
private MockHttpServletResponse response;
private HandlerAdapter handlerAdapter;
#Before
public void setUp() throws Exception {
this.request = new MockHttpServletRequest();
this.response = new MockHttpServletResponse();
this.handlerAdapter = applicationContext.getBean(HandlerAdapter.class);
}
ModelAndView handle(HttpServletRequest request, HttpServletResponse response)
throws Exception {
final HandlerMapping handlerMapping = applicationContext.getBean(HandlerMapping.class);
final HandlerExecutionChain handler = handlerMapping.getHandler(request);
assertNotNull("No handler found for request, check you request mapping", handler);
final Object controller = handler.getHandler();
// if you want to override any injected attributes do it here
final HandlerInterceptor[] interceptors =
handlerMapping.getHandler(request).getInterceptors();
for (HandlerInterceptor interceptor : interceptors) {
final boolean carryOn = interceptor.preHandle(request, response, controller);
if (!carryOn) {
return null;
}
}
final ModelAndView mav = handlerAdapter.handle(request, response, controller);
return mav;
}
#Test
public void testProcessFormSubmission() throws Exception {
request.setMethod("POST");
request.setRequestURI("/simple-form");
request.setParameter("myNumber", "");
final ModelAndView mav = handle(request, response);
// test we're returned back to the form
assertViewName(mav, "simple-form");
// make assertions on the errors
final BindingResult errors = assertAndReturnModelAttributeOfType(mav,
"org.springframework.validation.BindingResult.myForm",
BindingResult.class);
assertEquals(1, errors.getErrorCount());
assertEquals("", errors.getFieldValue("myNumber"));
}
See my blog post on integration testing Spring's MVC annotations
Sure. There's no reason why your test can't instantiate its own DispatcherServlet, inject it with the various items which it would have in a container (e.g. ServletContext), including the location of the context definition file.
Spring comes with a variety of servlet-related MockXYZ classes for this purpose, including MockServletContext, MockHttpServletRequest and MockHttpServletResponse. They're not really "mock" objects in the usual sense, they're more like dumb stubs, but they do the job.
The servlet's test context would have the usual MVC-related beans, plus your beans to test. Once the servlet is initialized, create the mock requests and responses, and feed them into the servet's service() method. If request gets routed correctly, you can check the results as written to the mock response.
In upcoming spring 3.2 (SNAPSHOT available) or with spring-test-mvc (https://github.com/SpringSource/spring-test-mvc) you can do it like this:
first we emulate Validation as we do not want to test the validator, just want to know if validation is called.
public class LocalValidatorFactoryBeanMock extends LocalValidatorFactoryBean
{
private boolean fakeErrors;
public void fakeErrors ( )
{
this.fakeErrors = true;
}
#Override
public boolean supports ( Class<?> clazz )
{
return true;
}
#Override
public void validate ( Object target, Errors errors, Object... validationHints )
{
if (fakeErrors)
{
errors.reject("error");
}
}
}
this is our test class:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration
public class RegisterControllerTest
{
#Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
#Autowired
#InjectMocks
private RegisterController registerController;
#Autowired
private LocalValidatorFactoryBeanMock validator;
#Before
public void setup ( )
{
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
// if you want to inject mocks into your controller
MockitoAnnotations.initMocks(this);
}
#Test
public void testPostValidationError ( ) throws Exception
{
validator.fakeErrors();
MockHttpServletRequestBuilder post = post("/info/register");
post.param("name", "Bob");
ResultActions result = getMockMvc().perform(post);
// no redirect as we have errors
result.andExpect(view().name("info/register"));
}
#Configuration
#Import(DispatcherServletConfig.class)
static class Config extends WebMvcConfigurerAdapter
{
#Override
public Validator getValidator ( )
{
return new LocalValidatorFactoryBeanMock();
}
#Bean
RegisterController registerController ( )
{
return new RegisterController();
}
}
}