Recently I came into one project which is using Spring MVC
for web-tier in the architecture. It
gave me a chance to use Spring MVC Test framework and Mockito mock framework
together in the unit testing of all Spring MVC controllers in the
application. I found that both of them
provide very good functionalities to test the controllers and would like to
show what I did with them.
Spring MVC Test framework
Before when unit testing MVC controllers we usually use MockHttpServletRequest and MockHttpServletResponse and directly send this mock request to
the controllers to do the unit testing, now in Spring 3.2 there is a new test
framework which is specially used for testing Spring MVC. It is Spring MVC Test framework. With this test framework you can test your
controllers just like you test them within a web container but without starting
a web container.
Spring MVC Test framework provides much nicer testing framework
to cover many aspects of testing in Spring MVC. With this framework apart from testing
business logic within controllers we can also test inbound/outbound
request/response serialization (such as JSON request to Java and Java to JSON
response), request mapping, request validation, content negotiation, exception
handling and etc.
In order to use it
you can add the below dependency into your project POM file.
<properties>
<spring.version>3.2.0.RELEASE</spring.version>
</properties>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>provided</scope>
</dependency>
|
The key part of Spring MVC Test framework is MockMVC. MockMVC will simulate the internals of Spring MVC and MockMVC is the entry point for Spring MVS testing.
The first step of using Spring MVC testing is to instantiate
one instance of MockMVC.
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(locations={"file:src/main/webapp/WEB-INF/mvc-dispatcher-servlet.xml",
"classpath:/META-INF/applicationContextForTest.xml"})
@TestExecutionListeners({
DependencyInjectionTestExecutionListener.class})
public
class ProductControllerTest {
@Autowired
private WebApplicationContext wac;
private MockMvc
mockMvc;
@Before
public void setup() {
this.mockMvc
= MockMvcBuilders.webAppContextSetup(this.wac).build();
// Details are omitted for brevity
}
}
|
After MockMVC is
created you can use MockMVC to do the
testing. You can create one HTTP
request and specify all the details of the request such as HTTP method, content
type, request parameters and etc. Then you can send this request through MockMVC and then verify the results.
RequestBuilder
requestBuilder = MockMvcRequestBuilders.get("/welocme");
this.mockMvc.perform(requestBuilder).
andExpect(MockMvcResultMatchers.status().isOk()).
andExpect(MockMvcResultMatchers.model().attribute("welcome_message",
"Welcome to use product: DELL Insprson")).
andExpect(MockMvcResultMatchers.model().size(1)).
andExpect(MockMvcResultMatchers.view().name("welcome"));
|
Mockito
Mockito is another testing mock framework. I find that it is quite easy and convenient
to use. First if you want to use it in
your project you can the following dependency to your project POM file. Here I use the version 1.9.5.
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.9.5</version>
<scope>test</scope>
</dependency>
|
Mockito provides some annotations to simplify writing the
testing codes. @Mock and @InjectMocks
are the two annotations I am going to use.
@Mock is used to annotate the object to be mocked. In the unit testing when testing a method of
one object which has the dependency on another object, this dependent object
needs to be mocked. In my project there
is class called ProductController and another class ProductService.
ProductController uses ProductService to do actual work to serve the
requests ProductController is supposed to handle.
@Controller
@RequestMapping("/products")
public
class ProductController {
@Autowired
private ProductService productService;
// Details are omitted for brevity
}
|
When I test ProductController I have ProductService
as mock service so it can mock different responses from ProductService. Using @Mock annotation from Mockito it can
simply be done in my test class as the below:
@Mock
ProductService mockProductService;
|
Another step is to put this mock object into the object to
be tested. Mockito provides another very
useful annotation @InjectMocks to do. @InjectMocks is to annotate the object
where the mock object is injected into.
In my example it is ProductController. and I have the
following:
@InjectMocks
ProductController productController;
|
But in order to make mock object injection really happen
there is another thing needed to be done: invoke MockitoAnnotations.initMocks
method. So my test class will be as the
below:
public
class ProductControllerTest {
@InjectMocks
private ProductController productController;
@Mock
private ProductService
mockproductService;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
}
|
The basic functions of mock framework is to return a given
results when a specific method is invoke.
In Mockito it is done using Mockito.when(...).thenReturn(...)
public
class ProductControllerTest {
@InjectMocks
private ProductController productController;
@Mock
private ProductService
mockproductService;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
List<Product> products = new
ArrayList<Product>();
Product product1 = new Product();
product1.setId(new Long(1));
Product product2 = new Product();
product2.setId(new Long(2));
products.add(product1);
products.add(product2);
Mockito.when(mockproductService.findAllProducts()).thenReturn(products);
}
}
|
In the above example when findAllProducts
method in ProductService
is invoked the mocked ProductService will return a list
of Products specified before.
The below is the code snippet that shows using both Spring MVC Testing and Mockito together for testing a controller.
Put all together
The below is the code snippet that shows using both Spring MVC Testing and Mockito together for testing a controller.
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(locations={"file:src/main/webapp/WEB-INF/mvc-dispatcher-servlet.xml",
"classpath:/META-INF/applicationContextForTest.xml"})
@TestExecutionListeners({
DependencyInjectionTestExecutionListener.class})
public
class ProductControllerTest {
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
@InjectMocks
private ProductController productController;
@Mock
private ProductService
mockproductService;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
List<Product> products = new
ArrayList<Product>();
Product product1 = new Product();
product1.setId(new Long(1));
Product product2 = new Product();
product2.setId(new Long(2));
products.add(product1);
products.add(product2);
Mockito.when(mockproductService.findAllProducts()).thenReturn(products);
this.mockMvc =
MockMvcBuilders.webAppContextSetup(this.wac).build();
}
@Test
public void testMethod() throws Exception
{
List<Product> products = new
ArrayList<Product>();
Product product1 = new Product();
product1.setId(new Long(1));
Product product2 = new Product();
product2.setId(new Long(2));
products.add(product1);
products.add(product2);
RequestBuilder requestBuilder =
MockMvcRequestBuilders.get("/products");
this.mockMvc.perform(requestBuilder).
andExpect(MockMvcResultMatchers.status().isOk()).
andExpect(MockMvcResultMatchers.model().attribute("Products",
products)).
andExpect(MockMvcResultMatchers.model().size(2)).
andExpect(MockMvcResultMatchers.view().name("show_products"));
}
}
|
This code doesn't work for me, the mocked service is not used in the test. Can you post the contents of your applicationContextForTest.xml?
ReplyDeleteI'm having the same problem, the controller under test is using the real service instead of the mocked service (and consequently hitting the database during the unit test). How did you configure Spring so that the controller under test uses the mocked service?
ReplyDeleteI got this working by changing the instantiation of the MockMvc instance to this:
ReplyDeletethis.mockMvc = MockMvcBuilders.standaloneSetup(productController).build();
(productController is the controller you are testing)
THanks man!
DeleteThat solved all my problems!
Now i can test everything on my MVC using the MVC features!
Thanks man!
DeleteWasting hours to fix it by your comment.
I'm following your tutorial for setting up Spring MVC Controller JUnit test.
ReplyDeleteI'm getting following error.
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'mvcContentNegotiationManager': Error setting property values; nested exception is org.springframework.beans.PropertyBatchUpdateException; nested PropertyAccessExceptions (1) are:
PropertyAccessException 1: org.springframework.beans.MethodInvocationException: Property 'mediaTypes' threw exception; nested exception is java.lang.NoSuchMethodError: org.springframework.http.MediaType.valueOf(Ljava/lang/String;)Lorg/springframework/http/MediaType;
Can you tell my why this is happening. I have jackson in my class path.
I'll join the chorus - it doesn't work for me either. The controller contains the real service rather than the mock. I don't want to use the standaloneSetup. This must be possible but I don't know what I'm missing. If anyone cracked it then do post here.
ReplyDeleteI have a related (as yet unanswered) post on StackOverflow about this: http://stackoverflow.com/questions/19690907/spring-aspect-not-triggered-in-unit-test
ReplyDeleteHi Thanks For visit us:
ReplyDeleteARKA Softwares & Outsourcing is an IT Company focusing on software & Web development and providing offshore outsourcing solutions to enterprises worldwide. Outsourcing software work in india
We need to see the "applicationContextForTest.xml" file, please :)
ReplyDeleteExcellent post! Thanks for sharing these helpful tips with us.
ReplyDeleteMobile App Development Company Indore