A Note on Unit Test Spring MVC Applications – DEVELOPPARADISE
03/04/2018

A Note on Unit Test Spring MVC Applications

Introduction

This is a note on unit test Spring MVC applications.

Background

This is a note on the unit test of Spring MVC applications. Spring provides a “spring-test” package, it simulates the routing process from a URL to an MVC controller. The test frameworks can run the code in the controllers by the URLs. The two attached examples are identical, except that one of them uses “TestNG” and the other uses “JUnit“.

A Note on Unit Test Spring MVC Applications

The Maven Dependencies

To create a Spring MVC application and perform a Spring style unit test on it, you will need the following dependencies.

<dependencies>                  <!-- Servlet jars for compilation, provided by Tomcat -->         <dependency>             <groupId>org.apache.tomcat</groupId>             <artifactId>tomcat-servlet-api</artifactId>             <version>${tomcat.version}</version>             <scope>provided</scope>         </dependency>                   <!-- Spring MVC dependencies -->         <dependency>             <groupId>org.springframework</groupId>             <artifactId>spring-webmvc</artifactId>             <version>5.0.3.RELEASE</version>         </dependency>                  <dependency>             <groupId>com.fasterxml.jackson.core</groupId>             <artifactId>jackson-databind</artifactId>             <version>2.9.3</version>         </dependency>                  <!-- Test dependencies -->               <dependency>             <groupId>org.springframework</groupId>             <artifactId>spring-test</artifactId>             <version>5.0.0.RELEASE</version>             <scope>test</scope>         </dependency> </dependencies>

Depending on your choice of TestNG or JUnit, you will also need to selectively add one of the following dependencies.

<dependency>     <groupId>org.testng</groupId>     <artifactId>testng</artifactId>     <version>6.9.10</version>     <scope>test</scope> </dependency>
<dependency>     <groupId>junit</groupId>     <artifactId>junit</artifactId>     <version>4.12</version>     <scope>test</scope> </dependency>

The Example MVC Application

The simple MVC application in the attached examples has only one controller that is implemented in the “TestController” class.

package com.song.web.controller;      import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody;      import com.song.web.service.ExampleService;      @Controller public class TestController {          @Autowired     private ExampleService exampleService;          @ResponseBody     @RequestMapping(value = "/example-end-point", method = RequestMethod.GET)     public Object test() {         return exampleService.getAMap();     }      }

The “@Autowired” “ExampleService” is implemented as the following:

package com.song.web.service;      import java.util.HashMap;      import org.springframework.stereotype.Service;      @Service public class ExampleService{     public HashMap<String, String> getAMap() {         HashMap<String, String> map = new HashMap<String, String>();                 map.put("V", "This is from a Spring service.");                  return map;     } }

The MVC application is initialized by the “MVCInitializer” class.

package com.song.web.configuration;      import javax.servlet.FilterRegistration; import javax.servlet.ServletContext; import javax.servlet.ServletException;      import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.ScopedProxyMode; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistration; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.support     .AbstractAnnotationConfigDispatcherServletInitializer;      import com.song.web.filter.NocacheFilter; import com.song.web.service.ExampleService;      public class MVCInitializer extends     AbstractAnnotationConfigDispatcherServletInitializer {          @EnableWebMvc     @ComponentScan({ "com.song.web.controller" })     public static class MVCConfiguration implements WebMvcConfigurer {                  @Override         public void addResourceHandlers(ResourceHandlerRegistry registry) {             ResourceHandlerRegistration registration =  registry.addResourceHandler("/*");             registration.addResourceLocations("/");         }                  @Bean         @Scope(value = WebApplicationContext.SCOPE_REQUEST,             proxyMode = ScopedProxyMode.TARGET_CLASS)         public ExampleService exampleService() {             return new ExampleService();         }              }          @Override     protected Class<?>[] getServletConfigClasses() {         return new Class[] { MVCConfiguration.class };     }          @Override     public void onStartup(ServletContext servletContext)             throws ServletException {           FilterRegistration.Dynamic nocachefilter = servletContext                   .addFilter("nocachefilter", new NocacheFilter());           nocachefilter.addMappingForUrlPatterns(null, false, "/*");                    super.onStartup(servletContext);     }          @Override     protected String[] getServletMappings() {         return new String[] { "/api/*" };     }          @Override     protected Class<?>[] getRootConfigClasses() { return null; }  }

If you deploy and run the application, and if you issue a “GET” request to “http://localhost:8080/ut-spring-mvc-testng/api/example-end-point”, you can see the following response in the POSTMAN.

A Note on Unit Test Spring MVC Applications

Unit Test With TestNG

In order to initiate the Spring context in the unit test, you need a configuration class that implements the “WebMvcConfigurer” interface.

package com.song.web.controller;      import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;      import com.song.web.service.ExampleService;      @EnableWebMvc @ComponentScan({ "com.song.web.controller" }) public class TestMVCConfiguration implements WebMvcConfigurer {          @Bean     public ExampleService exampleService() {         return new ExampleService();     } }
  • The “@ComponentScan” annotation tells Spring where to find the controllers.
  • The “@Bean” function returns an instance of the “ExampleService” class. If you do not want to use the actual implementation, you can return a mock of the class for unit test purpose.

The “ControllerTest” class performs the test on the “TestController“.

package com.song.web.controller;      import java.util.HashMap;      import javax.servlet.http.HttpServletResponse;      import org.springframework.beans.factory.annotation.Autowired; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.testng.AbstractTestNGSpringContextTests; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; import org.testng.Assert; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test;      import com.fasterxml.jackson.databind.ObjectMapper;      @WebAppConfiguration @ContextConfiguration( classes = { TestMVCConfiguration.class }) public class ControllerTest extends AbstractTestNGSpringContextTests {          private MockMvc mockMvc;          @Autowired     private WebApplicationContext wac;          @BeforeMethod     public void setup() {         this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();     }          @Test     public void ADummyTest() {         MockHttpServletRequestBuilder request             = MockMvcRequestBuilders.get("/example-end-point");                  try {             ResultActions resultActions = mockMvc.perform(request);             MvcResult result = resultActions.andReturn();                          MockHttpServletResponse response = result.getResponse();             int status = response.getStatus();                          Assert.assertEquals(HttpServletResponse.SC_OK, status);                          ObjectMapper mapper = new ObjectMapper();             HashMap<String, String> data = new HashMap<String, String>();             mapper.readerForUpdating(data).readValue(response.getContentAsString());                          Assert.assertEquals(data.get("V"), "This is from a Spring service.");                      } catch(Exception ex) { Assert.fail("Failed - " + ex.getMessage()); }     } }

In order that TestNG can recognize the test as a Spring style unit test, the “ControllerTest” class needs to extend the “AbstractTestNGSpringContextTests” class.

A Note on Unit Test Spring MVC Applications

Unit Test With JUnit

If you want to use JUnit to perform the test, you can replace “testng” with “junit” in your POM dependencies.

package com.song.web.controller;  import java.util.HashMap;  import javax.servlet.http.HttpServletResponse;  import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.mock.web.MockHttpServletResponse; 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.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext;      import com.fasterxml.jackson.databind.ObjectMapper;           @RunWith( SpringJUnit4ClassRunner.class ) @WebAppConfiguration @ContextConfiguration( classes = { TestMVCConfiguration.class }) public class ControllerTest {          private MockMvc mockMvc;          @Autowired     private WebApplicationContext wac;          @Before     public void setup() {         this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();     }          @Test     public void ADummyTest() {         MockHttpServletRequestBuilder request             = MockMvcRequestBuilders.get("/example-end-point");                  try {             ResultActions resultActions = mockMvc.perform(request);             MvcResult result = resultActions.andReturn();                          MockHttpServletResponse response = result.getResponse();             int status = response.getStatus();                          Assert.assertEquals(HttpServletResponse.SC_OK, status);                          ObjectMapper mapper = new ObjectMapper();             HashMap<String, String> data = new HashMap<String, String>();             mapper.readerForUpdating(data).readValue(response.getContentAsString());                          Assert.assertEquals("This is from a Spring service.", data.get("V"));                      } catch(Exception ex) { Assert.fail("Failed - " + ex.getMessage()); }     } }

Instead of extending the “AbstractTestNGSpringContextTests” class, you need to annotate the “ControllerTest” class by “@RunWith( SpringJUnit4ClassRunner.class )“.

Test Without Spring Annotations

If you can manually create a controller instance, you can perform the unit tests without the Spring annotations.

@Controller public class TestController {          private ExampleService exampleService;          @Autowired     public TestController(final ExampleService exampleService) {         this.exampleService = exampleService;     }          @ResponseBody     @RequestMapping(value = "/example-end-point", method = RequestMethod.GET)     public Object test() {         return exampleService.getAMap();     }      }

In the “TestController”, we have a constructor to take the “ExampleService” so we can manually construct a functional controller instance.

package com.song.web.controller;      import java.util.HashMap;      import javax.servlet.http.HttpServletResponse;      import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.testng.Assert; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test;      import com.fasterxml.jackson.databind.ObjectMapper; import com.song.web.service.ExampleService;      public class ControllerTest {          private MockMvc mockMvc;          @BeforeMethod     public void setup() {         this.mockMvc = MockMvcBuilders                 .standaloneSetup(new TestController(new ExampleService())).build();     }          @Test     public void ADummyTest() {         MockHttpServletRequestBuilder request             = MockMvcRequestBuilders.get("/example-end-point");                  try {             ResultActions resultActions = mockMvc.perform(request);             MvcResult result = resultActions.andReturn();                          MockHttpServletResponse response = result.getResponse();             int status = response.getStatus();                          Assert.assertEquals(HttpServletResponse.SC_OK, status);                          ObjectMapper mapper = new ObjectMapper();             HashMap<String, String> data = new HashMap<String, String>();             mapper.readerForUpdating(data).readValue(response.getContentAsString());                          Assert.assertEquals(data.get("V"), "This is from a Spring service.");                      } catch(Exception ex) { Assert.fail("Failed - " + ex.getMessage()); }     } }

We can create the “MockMvc” instance by passing the controller instance to the “MockMvcBuilders .standaloneSetup()” method without going through the Spring context initializations and proceed with the unit test directly.

Points of Interest

  • This is a note on unit test Spring MVC applications.
  • I hope you like my postings and I hope this note can help you one way or the other.

History

  • 4/2/2018: First revision