How to unit test a Spring MVC annotated controller? - java

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.

Related

Re-direct requests to SideEffect Utility Classes

for a spring boot application that needs to be tested below is my query.
#CustomLog
#RestController
#RequestMapping("/my_path")
public class MyController {
#GetMapping(path = "**", produces = {MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity<JsonNode> fetchData(HttpServletRequest request){
... some code.....which also calls external apis.....
}
#PostMapping(path = "**", produces = {MediaType.APPLICATION_JSON_VALUE})
#ResponseBody
public ResponseEntity<Map<String, Object>> createMOI(HttpServletRequest request){
... some code.....which also calls external apis.....
}
}
My application calls an external service which now needs to be mocked.
this.webClient = WebClient.builder().baseUrl("http://localhost:9600/external_host_path")
.defaultHeader(HttpHeaders.CONTENT_TYPE,MediaType.APPLICATION_JSON_VALUE)
.build();
Mono<Pojo>responseMo = webClient.post().uri("/aGivenSubPath")
.accept(MediaType.APPLICATION_JSON).bodyValue(requestPoJo)
.retrieve().bodyToMono(Pojo.class).block();
I am calling my controller API with MVC as part of springtest
#RunWith(SpringRunner.class)
#SpringBootTest
public class MyControllerTest {
#Autowired
MyController controller;
#Before
public void setup() throws Exception {
this.mockMvc = standaloneSetup(this.controller).build();
}
#Test
public void testControl() throws Exception {
mockMvc
.perform(post("http://localhost:9600/my_path")
.contentType(MediaType.APPLICATION_JSON)
.content("{'someData':'[]'}"))
.andExpect(status().isAccepted())
.andReturn();
}
}
What I am looking for is to somehow proxy or side effect
http://localhost:9600/external_host_path
and redirect all calls made for this host to a custom Utility class which provides response based on the request params to the external host programatically.
I have seen multiple examples for mockito, wireMock, mockwebserver, mockserver etc
But most of them work on a given(static path)-when(static path called)-then(give static response).
I have many calls through out the flow and I already have the logic of the utility class to generate responses for provided request arguments.
Although I was not able to find a answer to redirect webserver request to sideEffect class,
For now atleast managing by Mockito's MockBean and Answer.
#RunWith(SpringRunner.class)
#SpringBootTest
public class MyControllerTest {
#Autowired
MyController controller;
#MockBean
MyExternalServiceClient serviceClient;
#Autowired
MySideEffectService sideEffect;
#Before
public void setup() throws Exception {
this.mockMvc = standaloneSetup(this.controller).build();
Mockito.when(serviceClient.getMethod(any(),anyBoolean())).thenAnswer((Answer) invocation -> {
Object[] args = invocation.getArguments();
Object mock = invocation.getMock();
return sideEffect.getMethod((Map<String, List<String>>) args[0], (Boolean) args[1]);
});
}
#Test
public void testControl() throws Exception {
mockMvc
.perform(post("http://localhost:9600/my_path")
.contentType(MediaType.APPLICATION_JSON)
.content("{'someData':'[]'}"))
.andExpect(status().isAccepted())
.andReturn();
}
}
Will still have a lookout for a way (Maybe TestContainers with image creation on the fly that will create a server with my mockCode, so that i can use hostname of this one and replace with existing real hostname)

RequestMapping not working in spring

I have simple controler:
public class Controller {
#RequestMapping(value = "/test", method = {RequestMethod.GET, RequestMethod.POST})
public void test(HttpServletResponse response) throws IOException {
System.out.println("HELLO WORLD");
}
}
When I start jetty server and go to localhost:8080/test I am not getting output System.out.println("HELLO WORLD");
I have nothing about this controler in web.xml.
Where is a problem? How can I get my controler?
Annotate your class with #Controller
like
#Controller
public class AppController {
#RequestMapping(value = "/test", method = {RequestMethod.GET, RequestMethod.POST})
public void test(HttpServletResponse response) throws IOException {
System.out.println("HELLO WORLD");
}
}
Check for base package registred under Component Scanning
What is Component Scan?
Component scanning with <context:component-scan base-package="<<your classes base package>>" /> in xml configuration and #ComponentScan in annotation based configuration is telling spring that it should search the class path for all the classes under<> and look at each class to see if it has a #Controller, or #Repository, or #Service, or #Component and if it does then Spring will register the class with the bean factory
#Controller
#RequestMapping(value="/")
public class AppController {
#RequestMapping(value = "/test", method = {RequestMethod.GET, RequestMethod.POST})
public void test(HttpServletResponse response) throws IOException {
System.out.println("HELLO WORLD");
}
}
add request mapping to your controller level then it works.

REST service test with JUnit using Java EE annotations

I use Java EE 6 running on GlassFish and Jersey.
How can I mock a request to this resource with some custom headers, and some other request details?
#Stateless
#Path("/user")
public class Resources {
#Contex
private HttpServletRequest request;
....
#GET
#Path("/settings")
#Produces("application/json")
#Consumes("text/plain")
public AccountSettings accountSettings() {
//check custom headers and request content do some stuff
return accountSettings;
}
....
}
And this is my java code, but I get null pointer exception on res.accountSettings() because request is still null.
#Mock
HttpServletRequest request= Mockito.mock(HttpServletRequest.class);
#Test
public void testResources() {
when(request.getHeader(HTTP_AUTHORIZATION_HEADER)).thenReturn("Basic wedwd");
Resources res=new Resources();
AccountSettings response=res.accountSettings();
}
Instead of create with new keyword, When I use this then I also get the resources null.
#Inject Resources resources
I believe you can do as follow with an #InjectMocks annotation, it outside any JAXRS context or CDI injection stuff, it treated as a regular java class.
#RunWith(MockitoJUnitRunner.class)
public class ResourcesTest {
#Mock HttpServletRequest request;
#InjectMocks Resources res;
#Test
public void testResources() {
// given
when(request.getHeader(HTTP_AUTHORIZATION_HEADER)).thenReturn("Basic wedwd");
// when
AccountSettings response = res.accountSettings();
// then
...
}
}

Testing Spring #MVC annotations

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

How to unit test a Spring MVC controller using #PathVariable?

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

Categories

Resources