Sunday, February 10, 2013

Use Spring MVC Test framework and Mockito to test controllers

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.


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"));
       

    }
}







7 comments:

  1. 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?

    ReplyDelete
  2. I'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?

    ReplyDelete
  3. I got this working by changing the instantiation of the MockMvc instance to this:

    this.mockMvc = MockMvcBuilders.standaloneSetup(productController).build();

    (productController is the controller you are testing)

    ReplyDelete
    Replies
    1. THanks man!

      That solved all my problems!

      Now i can test everything on my MVC using the MVC features!

      Delete
  4. I'm following your tutorial for setting up Spring MVC Controller JUnit test.
    I'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.

    ReplyDelete
  5. 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.

    ReplyDelete
  6. I have a related (as yet unanswered) post on StackOverflow about this: http://stackoverflow.com/questions/19690907/spring-aspect-not-triggered-in-unit-test

    ReplyDelete