Integration Testing of Spring MVC Applications: Migrating to Spring 3.2

When Spring Framework 3.2 was released, spring-test-mvc was officially included in this release under the name Spring MVC Test Framework. Although many things remained the same, there are some changes that might cause confusion when we are migrating our applications from Spring Framework 3.1 to 3.2.

This blog entry was written to help us to migrate our integration integration tests to Spring Framework 3.2. It describes how we can get the required dependencies with Maven and update our existing integration tests to use the Spring MVC Test Framework. As an example, we will migrate the example application of my previous blog entry from Spring Framework 3.1 to 3.2.

Getting the Required Dependencies

When our application uses Spring Framework 3.2, we should not use the standalone spring-test-mvc project in our integration tests. Instead, we should use the spring-test module of Spring Framework 3.2. We can get the required dependencies by following these steps:

  1. Declare the Hamcrest (version 1.3) dependency in our pom.xml file. This ensures that we can use the newest Hamcrest matchers in our tests.
  2. Add the JUnit dependency (version 4.10) in our pom.xml file and exclude the hamcrest-core dependency.
  3. Declare the Spring test (version 3.2.0.RELEASE) dependency in our pom.xml file.
  4. Declare the Spring Test DBUnit (version 1.0.0) dependency in our pom.xml file.
  5. Declare DBUnit (version 2.4.8) dependency in the pom.xml file.

The dependency declarations of our testing dependencies looks as follows:

<dependency>
	<groupId>org.hamcrest</groupId>
	<artifactId>hamcrest-all</artifactId>
	<version>1.3</version>
	<scope>test</scope>
</dependency>
<dependency>
	<groupId>junit</groupId>
	<artifactId>junit</artifactId>
	<version>4.10</version>
	<scope>test</scope>
	<exclusions>
		<exclusion>
			<artifactId>hamcrest-core</artifactId>
			<groupId>org.hamcrest</groupId>
		</exclusion>
	</exclusions>
</dependency>
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-test</artifactId>
	<version>3.2.0.RELEASE</version>
	<scope>test</scope>
</dependency>
<dependency>
	<groupId>com.github.springtestdbunit</groupId>
	<artifactId>spring-test-dbunit</artifactId>
	<version>1.0.0</version>
	<scope>test</scope>
</dependency>
<dependency>
	<groupId>org.dbunit</groupId>
	<artifactId>dbunit</artifactId>
	<version>2.4.8</version>
	<scope>test</scope>
</dependency>

Migrating Our Integration Tests

This section describes how we can migrate our integration tests from spring-test-mvc to Spring MVC Test Framework. We will start by taking a look at the changes made to package and method names, learn to configure our integration tests by using the Spring MVC Test framework and take look at changes which we have to make to our existing integration tests.

Changes in Package and Method Names

These change in package and method names are described in the following:

  • The package org.springframework.test.web.server has a new name. It is now called org.springframework.test.web.servlet.
  • The body() method of the MockHttpServletRequestBuilder class has been renamed to content().
  • The mimeType() method of the ContentResultMatchers class has been renamed to contentType().

As we can see, the changes are cosmetic and should not cause us a lot of trouble. Lets move on and find out how we can configure our integration tests when our application uses Spring Framework 3.2.

Note: If you notice that something is missing, please a leave comment and I will update this list.

Configuration Changes

The configuration of Spring MVC Test Framework is a bit different than the configuration of the spring-test-mvc project. These differences are addressed in this subsection.

The @WebAppConfiguration annotation can be used on a class level to declare that the application context used by that class is an instance of WebApplicationContext. The created WebApplicationContext can be injected to the test class by adding a WebApplicationContext field to the test class and annotating that field with the @Resource annotation. This means that we can get rid of the application context loader classes (GenericWebContextLoader and WebContextLoader) which we used to load and inject the WebApplicationContext into our test classes.

The Spring MVC Test Framework supports two configuration methods:

  • A new MockMvc object can be created by using the web application context that is injected to the test class.
  • Standalone setup where the tested controller and its dependencies are configured manually.

The configuration methods which used either an application context configuration class or configuration file are no longer supported. The reference documentation of Spring Framework provides more information about the configuration of the Spring MVC Test Framework.

We can configure our integration tests by following these steps:

  1. Annotate the test class with the @RunWith annotation. This annotation is used to declare the test runner which is used to run our integration test. The correct test runner is the SpringJUnit4TestRunner.
  2. Annotate the test class with the @ContextConfiguration annotation and set the used application context configuration class or file.
  3. Annotate the class with the @WebAppConfiguration annotation.
  4. Use the @TestExecutionListener annotation to add test execution listeners required by the Spring Test DBUnit. If you are not familiar with the Spring Test DBUnit, you might want to check out the second part of my spring-test-mvc tutorial. Naturally, we can skip this phase if our tests are not using database.
  5. Use the @DatabaseSetup annotation to specify the DBUnit dataset file that is used to initialize our database to a known state before tests are run. We can skip this test if our tests are not using database.
  6. Add a FilterChainProxy field to our test class and annotate it with the @Resource annotation. This field contains a reference to the Spring security filter chain. If we are not using Spring Security, we can skip this step.
  7. Add a WebApplicationContext field to our test class and annotate it with the @Resource annotation. This field contains a reference to the used web application context.
  8. Add a MockMvc field to our test class. This field contains a reference to the MockMvc object that is used in our integration tests.
  9. Create a public setUp() method and annotate this method with the @Before annotation. This method creates a new MockMvc object by using the static webAppContextSetup() method of the MockMvcBuilders class and adds the Spring Security filter chain into our test (this is naturally not required if our test test does not use Spring Security)

The source code of our integration test skeleton class looks as follows:

import com.github.springtestdbunit.DbUnitTestExecutionListener;
import com.github.springtestdbunit.annotation.DatabaseSetup;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import javax.annotation.Resource;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {ExampleApplicationContext.class})
//@ContextConfiguration(locations = {"classpath:exampleApplicationContext.xml"})
@WebAppConfiguration
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
        DirtiesContextTestExecutionListener.class,
        TransactionalTestExecutionListener.class,
        DbUnitTestExecutionListener.class })
@DatabaseSetup("toDoData.xml")
public class ITTest {

    @Resource
    private FilterChainProxy springSecurityFilterChain;

    @Resource
    private WebApplicationContext webApplicationContext;

    private MockMvc mockMvc;

    @Before
    public void setUp() {
        mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
                .addFilter(springSecurityFilterChain)
                .build();
    }

    //Add tests here
}

Writing an Integration Test

This subsection describes the common testing utilities which are used in our integration tests and demonstrates how we can write an integration test by using the Spring MVC Test Framework.

Common Testing Utilities

Our integration tests are using four testing utilities that are described in the following:

  • The TodoTestUtil class is used in the unit and integration tests or our todo application.
  • The IntegrationTestUtil class is used only in our integration tests.
  • The DBUnit dataset is used to initialize our database into a known state before our tests are run.
  • The SecurityRequestPostProcessor class is used to create security context for Spring Security.

Lets move on and take a closer look at these testing utilities.

The TodoTestUtil class has one static method that is used in our integration tests. The TodoDTO createDTO(Long id, String description, String title) method is used to create new TodoDTO objects. The source code of the TodoTestUtil class looks as follows:

import net.petrikainulainen.spring.testmvc.todo.dto.TodoDTO;

public class TodoTestUtil {

    public static TodoDTO createDTO(Long id, String description, String title) {
        TodoDTO dto = new TodoDTO();

        dto.setId(id);
        dto.setDescription(description);
        dto.setTitle(title);

        return dto;
    }
}

The IntegrationTestUtil class has two purposes:

First, it declares a constact called APPLICATION_JSON_UTF8 that is used to set the content type and character set of HTTP requests and to verify the content type and character set of HTTP responses.

Second, it contains a static byte[] convertObjectsToJsonBytes(Object object) method that is used to convert object to bytes that contains the JSON representation of the object. The implementation of this method consists of following steps:

  1. Create a new ObjectMapper object.
  2. Configure the created object to include only non null properties of the serialized object.
  3. Convert the object as json string and return the created string as byte array.

The source code of the IntegrationTestUtil class looks as follows:

import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.annotate.JsonSerialize;
import org.springframework.http.MediaType;

import java.io.IOException;
import java.nio.charset.Charset;

public class IntegrationTestUtil {

    public static final MediaType APPLICATION_JSON_UTF8 = new MediaType(MediaType.APPLICATION_JSON.getType(), MediaType.APPLICATION_JSON.getSubtype(), Charset.forName("utf8"));

    public static byte[] convertObjectToJsonBytes(Object object) throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        mapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_NULL);
        return mapper.writeValueAsBytes(object);
    }
}

The DBUnit dataset which is used to initialize our database into a known state before our tests are run is called toDoData.xml and its content looks as follows:

<dataset>
    <todos id="1" creation_time="2012-10-21 11:13:28" description="Lorem ipsum" modification_time="2012-10-21 11:13:28" title="Foo" version="0"/>
    <todos id="2" creation_time="2012-10-21 11:13:28" description="Lorem ipsum" modification_time="2012-10-21 11:13:28" title="Bar" version="0"/>
</dataset>

The implementation of the SecurityRequestPostProcessor class is copied from the samples of the spring-test-mvc project. Its static UserDetailsRequestPostProcessor userDetailsService(String username) method is used to create a security context for Spring Security and set the logged in user by using the configured UserDetailsService (The username given as parameter specifies the used principal).

Add Todo Entry as Logged In User

As an example, we write an integration test which ensures that a logged in user can add new todo entries to the database. We can write this test by following these steps:

  1. Use the @ExpectedDatabase annotation to ensure that a new todo entry is added to the database.
  2. Create a new TodoDTO object and set its title and description.
  3. Perform a POST request to url '/api/todo/'. Set the content type of the request to 'application/json'. Set the character set of the request to 'UTF8'. Transform the created TodoDTO object into correct format and send it in the body of the request. If you are migrating your tests to Spring Framework 3.2, remember to use the content() method of the MockHttpServletRequestBuilder class instead of the body() method.
  4. Use static userDetailsService() method of the SecurityRequestPostProcessor class to set the logged in user. The username given as a method parameter specifies the user that is used when the request is performed.
  5. Verify that the 200 HTTP status code is returned.
  6. Verify that the content type of the response is 'application/json' and its character set is 'UTF8'. If you are migrating your tests to Spring Framework 3.2, remember to use the contentType() method of ContentResultMatchers class instead of the mimeType() method.
  7. Verify that the information of the added todo entry is returned correctly.

The source code of our integration test looks as follows:

import com.github.springtestdbunit.DbUnitTestExecutionListener;
import com.github.springtestdbunit.annotation.DatabaseSetup;
import com.github.springtestdbunit.annotation.ExpectedDatabase;
import com.github.springtestdbunit.assertion.DatabaseAssertionMode;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import javax.annotation.Resource;

import static org.springframework.test.web.server.samples.context.SecurityRequestPostProcessors.userDetailsService;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {ExampleApplicationContext.class})
//@ContextConfiguration(locations = {"classpath:exampleApplicationContext.xml"})
@WebAppConfiguration
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
        DirtiesContextTestExecutionListener.class,
        TransactionalTestExecutionListener.class,
        DbUnitTestExecutionListener.class })
@DatabaseSetup("toDoData.xml")
public class ITTodoControllerTest {

    //Add FilterChainProxy and WebApplicationContext here

    private MockMvc mockMvc;

	//Add the setUp() method here

    @Test
    @ExpectedDatabase(value="toDoData-add-expected.xml", assertionMode = DatabaseAssertionMode.NON_STRICT)
    public void addAsUser() throws Exception {
        TodoDTO added = TodoTestUtil.createDTO(null, "description", "title");
        mockMvc.perform(post("/api/todo")
                .contentType(IntegrationTestUtil.APPLICATION_JSON_UTF8)
                .content(IntegrationTestUtil.convertObjectToJsonBytes(added))
                .with(userDetailsService("user"))
        )
                .andExpect(status().isOk())
                .andExpect(content().contentType(IntegrationTestUtil.APPLICATION_JSON_UTF8))
                .andExpect(content().string("{\"id\":3,\"description\":\"description\",\"title\":\"title\"}"));
    }
}

The DBUnit dataset file which is used to verify that a new todo entry is added to the database is toDoData-add-expected.xml and its content looks as follows:

<dataset>
    <todos id="1" description="Lorem ipsum" title="Foo" version="0"/>
    <todos id="2" description="Lorem ipsum" title="Bar" version="0"/>
    <todos id="3" description="description" title="title" version="0"/>
</dataset>

That is All Folks!

We now know how we can migrate our existing integration tests from spring-test-mvc to Spring MVC Test Framework. This tutorial has taught us two things:

  • We know how we can configure our integration tests when we are using Spring Framework 3.2.
  • We know how we can migrate our existing integration tests to use the Spring MVC Test Framework instead of the spring-test-mvc project.

The example application of this blog entry provides more examples about different integration tests which use the Spring MVC Test Framework. As always, you can get the example application from Github.

If you want to save time by writing less test code, take a look at my upcoming Test With Spring Course.
10 comments… add one
  • Ravi May 1, 2013 @ 11:04

    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.

    • Petri May 1, 2013 @ 11:35

      Hi Ravi,

      check that all Spring jars found from the classpath belongs to the same Spring version.

  • jack Jul 31, 2013 @ 16:37

    How would i test the method which has input as @RequestParameter

    • Petri Jul 31, 2013 @ 18:31

      You can use the param(String name, String... values) method of the MockHttpServletRequestBuilder class.

      Let's assume that you want to test a controller method which processes GET requests send to url '/hello'. This controller method has one request parameter called 'message'.

      The source code which tests this controller method looks as follows:

      
      mockMvc.perform(get("/hello")
      	.param("message", "World")
      )
      	.andExpect(status().isOk());
      
      

      I hope that this answered to your question.

  • Vity Aug 22, 2013 @ 10:51

    For me @Resource
    private FilterChainProxy springSecurityFilterChain;
    did not work. This code helped me to run it (copied from Spring-mvc-test project). But thank you very much for your helpful article anyway.

    
    @Autowired
    private WebApplicationContext wac;
    
    @Autowired
    private FilterChainProxy springSecurityFilterChain;
    
    private MockMvc mockMvc;
    
    @Before
    public void setup() {
    	this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac)
    		.addFilters(this.springSecurityFilterChain)
    		.build();
    }   
    
    
    • Petri Kainulainen Aug 22, 2013 @ 17:15

      I am happy to her that you could solve your problem.

      By the way, if you are interested to learn the difference of the @Resource and the @Autowired annotation, you should check out the following StackOverflow question: @Resouce vs @Autowired.

  • Adarsh Mar 17, 2015 @ 15:44

    hi
    I am able to create every things for me its authenticating also but when i want to invoke another service in the controller its says 401 could u please help me in resolving this

    i am using post man to test my rest service
    a.login is successful getting 200
    b.for another service its saying error what i understand till now its not having any sessionid for token for authentication so how could i resolve this problem .

    • Petri Mar 17, 2015 @ 20:32

      Have you followed the instructions given in my blog post that explains how you can write security tests with Spring MVC Test? If you have done that, I need to see your integration tests because I need more information before I can figure out what is wrong.

  • Rahul May 6, 2016 @ 13:31

    Hi, I am getting bellow error as :

    "java.lang.NoSuchMethodError: org.springframework.mock.web.MockFilterChain.(Ljavax/servlet/Servlet;[Ljavax/servlet/Filter;)V
    at org.springframework.test.web.servlet.MockMvcBuilderSupport.createMockMvc(MockMvcBuilderSupport.java:60)
    at org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder.build(DefaultMockMvcBuilder.java:207)
    at tutorial.mvc.BlogEntryControllerTest.setup(BlogEntryControllerTest.java:27)
    "

    I am unable to figure it out,
    As you said in earlier post related to this error, I have added jars of same version
    "commons-logging-1.1.3.jar,jackson-core-2.7.0.jar
    jackson-annotations-2.7.0.jar,jackson-databind-2.7.0.jar,
    javax.servlet-api.jar, junit-4.12.jar,mockito-all-1.9.5.jar,spring-beans-3.2.2.release.jar,
    spring-context-3.2.2.RELEASE.jar,spring-context-support-3.2.2.RELEASE-sources.jar,
    spring-core-3.2.2.RELEASE.jar,spring-expression-3.2.2.RELEASE.jar,spring-mock-2.0.6.jar,
    spring-test-3.2.2.RELEASE.jar,spring-web-3.2.2.RELEASE.jar,spring-webmvc-3.2.2.RELEASE.jar"

    Please help me

    • Petri May 17, 2016 @ 9:27

      Hi,

      This problem is probably caused by dependency mismatch or a missing dependency. Unfortunately I cannot provide the solution to you since I cannot access your project, but I think that you should double check your dependency versions and compare them with the dependencies used in the example application of this blog post.

Leave a Reply