Integration Testing of Spring MVC Applications: Controllers

This is the second part of my spring-test-mvc tutorial and it describes how we can write integration tests for “normal” controller methods (methods that are not using Ajax or processing form submissions).

During this tutorial, we write integration tests for a simple todo application that provides CRUD functions for todo entries. This tutorial concentrates on three functions that are used to view a list of todo entries, view the information of a single todo entry and delete a single todo entry from the database.

Lets get started, shall we?

This tutorial assumes that we are already familiar with the concepts described in the first part of this tutorial.

Getting The Required Dependencies

We can get the required dependencies with Maven 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. Exclude the hamcrest-core dependency of JUnit.
  3. Declare the Spring test (version 3.1.2) dependency in our pom.xml file.
  4. Exclude the hamcrest-all dependency of spring-test-mvc.
  5. Declare the Spring Test DBUnit (version 1.0.0) dependency in our pom.xml file.
  6. Declare DBUnit (version 2.4.8) dependency in 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.1.2.RELEASE</version>
	<scope>test</scope>
</dependency>
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-test-mvc</artifactId>
	<version>1.0.0.M2</version>
	<scope>test</scope>
	<exclusions>
		<exclusion>
			<artifactId>hamcrest-all</artifactId>
			<groupId>org.hamcrest</groupId>
		</exclusion>
	</exclusions>
</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>

Crash Course to Spring Test DBUnit

We use the Spring Test DBUnit library to provide an integration between the Spring test framework and the DBUnit database testing library. This section describes how we can configure Spring Test DBUnit and use it in our integration tests.

Configuring Spring Test DBUnit

We can configure the Spring Test DBUnit by following these steps:

  1. Create an application context configuration class that declares the data source bean that provides database access for Spring Test DBUnit.
  2. Configure our test case.

These steps are described with more details in following.

Creating an Application Context Configuration Class

We can create an application context configuration class by following these steps:

  1. Annotate the class with the @Configuration annotation.
  2. Import the application.properties file by using the @PropertySource annotation.
  3. Add a field which type is Environment to the configuration class and annotate this field with the @Resource annotation.
  4. Add the configuration of the data source bean to the configuration class.

The source code of the ExampleApplicationContext class looks as follows:

import com.jolbox.bonecp.BoneCPDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;

import javax.annotation.Resource;
import javax.sql.DataSource;

@Configuration
@PropertySource("classpath:application.properties")
public class ExampleApplicationContext {

    @Resource
    private Environment environment;

    @Bean
    public DataSource dataSource() {
        BoneCPDataSource dataSource = new BoneCPDataSource();

        dataSource.setDriverClass(environment.getRequiredProperty("db.driver"));
        dataSource.setJdbcUrl(environment.getRequiredProperty("db.url"));
        dataSource.setUsername(environment.getRequiredProperty("db.username"));
        dataSource.setPassword(environment.getRequiredProperty("db.password"));

        return dataSource;
    }
}

Configuring Our Integration Test

We can configure our integration test by following these steps:

  1. Configure the used test runner by annotation the test class with the @RunWith annotation. The correct test runner class is the SpringJUnit4ClassRunner.
  2. Annotate the test class with the @ContextConfiguration annotation and provide the type of our application context configuration class as its value.
  3. Configure the required test execution listeners by annotating the test class with the @TestExecutionListeners annotation and provide the DbUnitTestExecutionListener and the standard Spring listeners as its value. The DBUnitTestExecution listener ensures that Spring process the DBUnit annotations that are used to configure the used data sets.

The source code of a test class that uses Spring Test DBUnit is given in following:

import com.github.springtestdbunit.DbUnitTestExecutionListener;
import org.junit.runner.RunWith;
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 javax.annotation.Resource;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {ExampleApplicationContext.class})
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
        DirtiesContextTestExecutionListener.class,
        TransactionalTestExecutionListener.class,
        DbUnitTestExecutionListener.class })
public class Test {

    //Add test methods here
}

Using Spring Test DBUnit

The homepage of the Spring Test DBUnit project describes how we can use annotations to initalize the database to a known state before our run, reset the database tables after our test has been completed and verify the contents of the database once our test is finished. During this tutorial we use two annotations that are described in following:

  • The @DatabaseSetup annotation is used to initialize the database to a known state before a test is run.
  • The @ExpectedDatabase annotation is used to verify the contents of the used database after a test is finished.

Crash Course to spring-test-mvc

We can write an integration test with spring-test-mvc by following these steps:

  1. Create the executed request and execute it.
  2. Verify that the response is correct.

Both of these steps are described with more details in the following subsections.

Creating And Executing Requests

We can create the executed request by using the perform(RequestBuilder requestBuilder) method of the MockMvc class. The MockMvcRequestBuilders class provides static methods that we can use to create the actual request builders. These methods are described in the following:

  • get() method creates a request builder for GET request.
  • delete() method creates a request builder for DELETE request.
  • fileUpload() creates a request builder for multipart request.
  • post() method creates a request builder for POST request.
  • put() method creates a request builder for PUT method.

We can get more details about building the executed requests by taking a look at the MockHttpServletRequestBuilder class.

Verifying The Response

The ResultActions interface declares methods that we can use to apply actions to the result of an executed requests. These methods are described in the following:

  • void andExpect(ResultMatcher matcher) method is used to specify assertions to the result of the executed query.
  • void andDo(ResultHandler handler) method is used to apply an action to the result of the request.
  • MvcResult andReturn() method returns the result of the request.

We can get more information about the actions that we can apply to the result of the executed requests by taking a look at the following classes:

  • The MockMvcResultMatchers class is used to specify assertions to the result of the request.
  • The MockMvcResultHandlers class for finding out out what kind of actions we can perform to the result of the executed request.

Writing Integration Tests for Controllers

We are now ready to write integration tests for our application. Lets start by taking a quick look at the anatomy of our todo application.

The Anatomy of Our Todo Application

This section describes the common parts of our example application and its goal is to help us to understand the rest of this blog entry. To be more specific, we will take a closer look at the domain model, the service interface and the controller.

The Domain Model

The domain model of our example application consists of a single entity called Todo and its looks as follows:

import org.hibernate.annotations.Type;
import org.joda.time.DateTime;
import javax.persistence.*;

@Entity
@Table(name="todos")
public class Todo {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(name = "creation_time", nullable = false)
    @Type(type="org.jadira.usertype.dateandtime.joda.PersistentDateTime")
    private DateTime creationTime;

    @Column(name = "description", nullable = true, length = 500)
    private String description;

    @Column(name = "modification_time", nullable = false)
    @Type(type="org.jadira.usertype.dateandtime.joda.PersistentDateTime")
    private DateTime modificationTime;

    @Column(name = "title", nullable = false, length = 100)
    private String title;

    @Version
    private long version;

    public Todo() {

    }

    //Getters and other methods
}

The Service Interface

In order to understand the implementation of our controller methods, we must understand the contract between our controller class and the service layer. This contract is described by the TodoService interface. This interface declares the following methods:

  • Todo deleteById(Long id) method deletes a single todo entry and returns the deleted entry. If the deleted todo entry is not found, this method throws TodoNotFoundException.
  • List findAll() method returns a list of todo entries. If no todo entries is found, this method returns an empty list.
  • Todo findById(Long id) returns a todo entry by using the given id as a search criteria. If no todo entry is found, this method throws TodoNotFoundException.

The source code of the TodoService interface looks as follows:

public interface TodoService {

    public Todo deleteById(Long id) throws TodoNotFoundException;

    public List<Todo> findAll();

    public Todo findById(Long id) throws TodoNotFoundException;
}

The Controller

The name of our controller class is TodoController. This class provides controller methods that can be used to view a todo list page, view the information of a single todo entry and delete a single todo entry. The source code of the TodoController class looks as follows:

import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import javax.annotation.Resource;
import javax.validation.Valid;

@Controller
public class TodoController {

    @Resource
    private TodoService service;

    @Resource
    private MessageSource messageSource;

    @RequestMapping(value = "/todo/delete/{id}", method = RequestMethod.GET)
    public String deleteById(@PathVariable("id") Long id, RedirectAttributes attributes) throws TodoNotFoundException {
        Todo deleted = service.deleteById(id);

        addFeedbackMessage(attributes, "feedback.message.todo.deleted", deleted.getTitle());

        return createRedirectViewPath("/");
    }

    @RequestMapping(value = "/", method = RequestMethod.GET)
    public String findAll(Model model) {
        List<Todo> models = service.findAll();

        model.addAttribute("todos", models);

        return "todo/list";
    }

    @RequestMapping(value = "/todo/{id}", method = RequestMethod.GET)
    public String findById(@PathVariable("id") Long id, Model model) throws TodoNotFoundException {
        Todo found = service.findById(id);

        model.addAttribute("todo", found);

        return "todo/view";
    }

    private void addFeedbackMessage(RedirectAttributes attributes, String messageCode, Object... messageParameters) {
        String localizedFeedbackMessage = getMessage(messageCode, messageParameters);
        attributes.addFlashAttribute("feedbackMessage", localizedFeedbackMessage);
    }

    private String getMessage(String messageCode, Object... messageParameters) {
        Locale current = LocaleContextHolder.getLocale();
        return messageSource.getMessage(messageCode, messageParameters, current);
    }

    private String createRedirectViewPath(String requestMapping) {
        StringBuilder redirectViewPath = new StringBuilder();
        redirectViewPath.append("redirect:");
        redirectViewPath.append(requestMapping);
        return redirectViewPath.toString();
    }
}

Creating The Integration Test Skeleton Class

We can now combine the lessons learned from the first part of this tutorial and this blog entry, and create a skeleton class for our integration tests. We can do this by following these steps:

  1. Configure spring-test-mvc by using the technique described in the first part of this tutorial.
  2. Configure Spring Test DBUnit using the technique that was explained earlier in this blog entry.
  3. Configure the DBUnit databaset that is used to initialize our database to a known state before our tests are run by using the @DatabaseSetup annotation.

The relevant parts of the ITTodoControllerTest 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.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.web.server.MockMvc;
import org.springframework.test.web.server.samples.context.WebContextLoader;
import org.springframework.test.web.server.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import javax.annotation.Resource;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader = WebContextLoader.class, classes = {ExampleApplicationContext.class})
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
        DirtiesContextTestExecutionListener.class,
        TransactionalTestExecutionListener.class,
        DbUnitTestExecutionListener.class })
@DatabaseSetup("toDoData.xml")
public class ITTodoControllerTest {

    @Resource
    private WebApplicationContext webApplicationContext;

    private MockMvc mockMvc;

    @Before
    public void setUp() {
        mockMvc = MockMvcBuilders.webApplicationContextSetup(webApplicationContext)
                .build();
    }
    
    //Add tests here
}

The name of our DBUnit dataset file is 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>

Writing Integration Tests for Controller Methods

We are now finally ready to write integration tests for our controller methods. The steps required to write integration tests for each controller method are described with more details in following.

View Todo List

The controller method that is used to view the todo list is called findAll(). We can write an integration test for this method by following these steps:

  1. Use @ExpectedDatabase annotation to verify that no changes are made to the database.
  2. Perform a GET request to url '/'.
  3. Verify that the HTTP status code is 200.
  4. Verify that name of the rendered view is 'todo/list'.
  5. Verify that the request is forwarded to url '/WEB-INF/jsp/todo/list.jsp'.
  6. Verify that the size of the todo list is 2.
  7. Verify that the todo list contains the correct items.

The source code of our test case looks as follows:

import com.github.springtestdbunit.DbUnitTestExecutionListener;
import com.github.springtestdbunit.annotation.DatabaseSetup;
import com.github.springtestdbunit.annotation.ExpectedDatabase;
import org.junit.Test;
import org.junit.runner.RunWith;
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.web.server.MockMvc;
import org.springframework.test.web.server.samples.context.WebContextLoader;

import static org.hamcrest.Matchers.*;
import static org.springframework.test.web.server.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.server.result.MockMvcResultMatchers.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader = WebContextLoader.class, classes = {ExampleApplicationContext.class})
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
        DirtiesContextTestExecutionListener.class,
        TransactionalTestExecutionListener.class,
        DbUnitTestExecutionListener.class })
@DatabaseSetup("toDoData.xml")
public class ITTodoControllerTest {

    //Add web application context here

    private MockMvc mockMvc;

    //Add setUp() method here

    @Test
    @ExpectedDatabase("toDoData.xml")
    public void findAll() throws Exception {
        mockMvc.perform(get("/"))
                .andExpect(status().isOk())
                .andExpect(view().name("todo/list"))
                .andExpect(forwardedUrl("/WEB-INF/jsp/todo/list.jsp"))
                .andExpect(model().attribute("todos", hasSize(2)))
                .andExpect(model().attribute("todos", hasItem(
                        allOf(
                                hasProperty("id", is(1L)),
                                hasProperty("description", is("Lorem ipsum")),
                                hasProperty("title", is("Foo"))
                        )
                )))
                .andExpect(model().attribute("todos", hasItem(
                        allOf(
                                hasProperty("id", is(2L)),
                                hasProperty("description", is("Lorem ipsum")),
                                hasProperty("title", is("Bar"))
                        )
                )));
    }
}

View Todo Entry

The findById() method of the TodoController class is used to view the information of a single todo entry. We have to write two integration tests for this function:

  1. We have to ensure that the information of the todo entry is shown if a todo entry is found.
  2. We have to ensure that the 404 page is shown if no todo entry is found.

We can write the first integration test by following these steps:

  1. Use the @ExcectedDatabase annotation to ensure that no changes are made to the database.
  2. Perform a GET request to url '/todo/1'.
  3. Verify that the HTTP status code is 200.
  4. Verify that the name of the rendered view is 'todo/view'.
  5. Verify that the request is forwarded to url '/WEB-INF/jsp/todo/view.jsp'.
  6. Verify that the model contains the information of the found todo entry.

The source code of the first integration test looks as follows:

import com.github.springtestdbunit.DbUnitTestExecutionListener;
import com.github.springtestdbunit.annotation.DatabaseSetup;
import com.github.springtestdbunit.annotation.ExpectedDatabase;
import org.junit.Test;
import org.junit.runner.RunWith;
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.web.server.MockMvc;
import org.springframework.test.web.server.samples.context.WebContextLoader;

import static org.hamcrest.Matchers.*;
import static org.springframework.test.web.server.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.server.result.MockMvcResultMatchers.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader = WebContextLoader.class, classes = {ExampleApplicationContext.class})
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
        DirtiesContextTestExecutionListener.class,
        TransactionalTestExecutionListener.class,
        DbUnitTestExecutionListener.class })
@DatabaseSetup("toDoData.xml")
public class ITTodoControllerTest {

    //Add web application context here

    private MockMvc mockMvc;

    //Add setUp() method here

    @Test
    @ExpectedDatabase("toDoData.xml")
    public void findById() throws Exception {
        mockMvc.perform(get("/todo/{id}", 1L))
                .andExpect(status().isOk())
                .andExpect(view().name("todo/view"))
                .andExpect(forwardedUrl("/WEB-INF/jsp/todo/view.jsp"))
                .andExpect(model().attribute("todo", hasProperty("id", is(1L))))
                .andExpect(model().attribute("todo", hasProperty("description", is("Lorem ipsum"))))
                .andExpect(model().attribute("todo", hasProperty("title", is("Foo"))));
    }
}

We can write the second integration test by following these steps:

  1. Use the @ExpectedDatabase annotation to verify that no changes are made to the database.
  2. Perform a GET request to url '/todo/3'.
  3. Verify that the HTTP status code is 404.
  4. Verify that the name of the view is 'error/404'.
  5. Verify that the request is forwarded to url '/WEB-INF/jsp/error/404.jsp'.

The source code of the second integration test looks as follows:

import com.github.springtestdbunit.DbUnitTestExecutionListener;
import com.github.springtestdbunit.annotation.DatabaseSetup;
import com.github.springtestdbunit.annotation.ExpectedDatabase;
import org.junit.Test;
import org.junit.runner.RunWith;
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.web.server.MockMvc;
import org.springframework.test.web.server.samples.context.WebContextLoader;

import static org.springframework.test.web.server.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.server.result.MockMvcResultMatchers.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader = WebContextLoader.class, classes = {ExampleApplicationContext.class})
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
        DirtiesContextTestExecutionListener.class,
        TransactionalTestExecutionListener.class,
        DbUnitTestExecutionListener.class })
@DatabaseSetup("toDoData.xml")
public class ITTodoControllerTest {

    //Add web application context here

    private MockMvc mockMvc;

    //Add setUp() method here

    @Test
    @ExpectedDatabase("toDoData.xml")
    public void findByIdWhenTodoIsNotFound() throws Exception {
        mockMvc.perform(get("/todo/{id}", 3L))
                .andExpect(status().isNotFound())
                .andExpect(view().name("error/404"))
                .andExpect(forwardedUrl("/WEB-INF/jsp/error/404.jsp"));
    }
}

Delete Todo Entry

The deleteById() method of the TodoController class is used to delete a todo entry from the database. We have to write two integration tests for it:

  1. We have to ensure that the found todo entry is deleted.
  2. We have to ensure that if no todo entry is found, a 404 page is shown.

We can write the first integration test by following these steps:

  1. Use the @ExpectedDatabase annotation to specify the DBUnit dataset that is used to verify that the todo entry has been deleted from the database.
  2. Perform a GET request to url '/todo/delete/1'.
  3. Verify that the HTTP status code is 200.
  4. Verify that name of the view is 'redirect:/'.
  5. Verify that the correct feedback message is given as a flash attribute.

The source code of the first integration test looks as follows:

import com.github.springtestdbunit.DbUnitTestExecutionListener;
import com.github.springtestdbunit.annotation.DatabaseSetup;
import com.github.springtestdbunit.annotation.ExpectedDatabase;
import org.junit.Test;
import org.junit.runner.RunWith;
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.web.server.MockMvc;
import org.springframework.test.web.server.samples.context.WebContextLoader;

import static org.hamcrest.Matchers.is;
import static org.springframework.test.web.server.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.server.result.MockMvcResultMatchers.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader = WebContextLoader.class, classes = {ExampleApplicationContext.class})
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
        DirtiesContextTestExecutionListener.class,
        TransactionalTestExecutionListener.class,
        DbUnitTestExecutionListener.class })
@DatabaseSetup("toDoData.xml")
public class ITTodoControllerTest {

    //Add web application context here

    private MockMvc mockMvc;

    //Add setUp() method here

    @Test
    @ExpectedDatabase("todoData-delete-expected.xml")
    public void deleteById() throws Exception {
        mockMvc.perform(get("/todo/delete/{id}", 1L))
                .andExpect(status().isOk())
                .andExpect(view().name("redirect:/"))
                .andExpect(flash().attribute("feedbackMessage", is("Todo entry: Foo was deleted.")));
    }
}

The content of the todoData-delete-expected.xml file looks as follows:

<dataset>
    <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>

We can write the second integration test by following these steps:

  1. Use the @ExpectedDatabase annotation to specify that no changes are made to the database.
  2. Perform a GET request to url '/todo/delete/3'
  3. Verify that the HTTP status code is 404.
  4. Verify that the name of the rendered view is 'error/404'.
  5. Verify that the request is forwarded to url '/WEB-INF/jsp/error/404.jsp'.

The source code of the second integration test looks as follows:

import com.github.springtestdbunit.DbUnitTestExecutionListener;
import com.github.springtestdbunit.annotation.DatabaseSetup;
import com.github.springtestdbunit.annotation.ExpectedDatabase;
import org.junit.Test;
import org.junit.runner.RunWith;
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.web.server.MockMvc;
import org.springframework.test.web.server.samples.context.WebContextLoader;

import static org.springframework.test.web.server.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.server.result.MockMvcResultMatchers.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader = WebContextLoader.class, classes = {ExampleApplicationContext.class})
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
        DirtiesContextTestExecutionListener.class,
        TransactionalTestExecutionListener.class,
        DbUnitTestExecutionListener.class })
@DatabaseSetup("toDoData.xml")
public class ITTodoControllerTest {

    //Add web application context here

    private MockMvc mockMvc;

    //Add setUp() method here

    @Test
    @ExpectedDatabase("toDoData.xml")
    public void deleteByIdWhenTodoIsNotFound() throws Exception {
        mockMvc.perform(get("/todo/delete/{id}", 3L))
                .andExpect(status().isNotFound())
                .andExpect(view().name("error/404"))
                .andExpect(forwardedUrl("/WEB-INF/jsp/error/404.jsp"));
    }
}

Conclusion

We have now learned how we can write integration tests for controller methods that do not process form submissions or use Ajax. This tutorial has taught us three things:

  • It is really easy to execute requests and specify assertions to the response of the executed request by using spring-test-mvc.
  • The integration tests written by using spring-test-mvc are very readable. Thus, they are an excellent way to document the functions of our application.
  • We cannot use spring-test-mvc to verify that the view is working properly but we can verify that the right view is rendered.

In the next part of this tutorial, we learn to write integration tests for controller methods that processes form submissions.

In the meantime, you can get the example application of this blog post from Github.

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

    Hi Petri,

    I still wonder where does that get() method comes from...

    Is it declared by one of these annotations ?

    Or inherited somehow ?

    Kind Regards,

    • Petri May 24, 2013 @ 19:15

      Hi Stephane,

      The get() method is a static method of the MockMvcRequestBuilders class. If you want to use it, you have add the following static import to your class:

      import static org.springframework.test.web.server.request.MockMvcRequestBuilders.get

      I hope that this answered to your question.

  • Stephane May 24, 2013 @ 17:46

    I just found out I add to import

    import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

    But my STS 3.2.0-RELEASE does not see it and a Shift-Ctrl-O will remove it.. strange.

    • Petri May 24, 2013 @ 19:18

      Take a look at my answer to your previous comment.

  • Sandeep Oct 20, 2013 @ 10:54

    Hi Petri, nice tutorial, as always. However I got problem when applying your idea to my multi-module project: when I ran my web project using Tomcat within Eclipse, the file in src/integration-test/resources is still in classpath and it takes precedence over the src/main/resources.
    Looks like the maven build helper is doing something wrong here. Or could it be the plugin only works with maven, not m2e and eclipse?

    • Petri Oct 20, 2013 @ 11:14

      Hi Sandeep,

      Thank you for your nice words. I really appreciate them.

      I have got a few questions about your problem:

      • I assume that you have the same file in both directories. What is the purpose of this file? If this is a some kind of configuration file, it might be a good idea to keep it only in the src/main/resources directory and use Maven properties filtering to set the profile specific configuration.
      • When you run your application, which profile do you use? If this happen when you run the application by using the dev profile, you might want to move the configuration of the Maven build helper plugin to the integration-test profile (more info about this).
      • Does this problem happen only in Eclipse or does it happen when you run the application from the command prompt?
  • Sandeep Oct 20, 2013 @ 13:53

    Thanks Petri for your reply.
    Yes, I have same file name in both directories. The idea is so that I can run tests with test-specific configurations without having to switch to another profile, which would result in a re-build of all projects.
    Before I add the build helper maven plugin to my POM, this approach works fine (test/resources gets priority when running tests, main/resources take precedence otherwise). After I added the plugin, it started giving the test/resources and integration-test/resources priority even when I don't run any tests. Do you think the build helper plugin has failed to observe Maven's classpath ordering convention (for test/non-test resources)?
    Apart from that, your suggestion solved my problem.
    Thank you.
    Sandeep.

    • Petri Oct 20, 2013 @ 22:23

      Hmmh. I think that it is possible that the builder helper doesn't follow the classpath ordering convention of Maven. Does the same problem happen when you run your web project from the command prompt?

      If it does, the problem is probably in the build helper plugin. If it doesn't happen, the problem is probably in the Eclipse Maven plugin.

  • Dirk Oct 23, 2013 @ 11:42

    Hi Petri.

    When testing MVC controllers (called by the DispatcherServlet) which accesses a database the real database must be mocked e.g. bei an XML file like your todoData.xml. Is this correct?
    Well, whent I test my controller (implementation) the methods to test oftenly contains code accessing a database (using a service which in turns uses a (Jpa)Repository which in turn returns an entity...), how is it accomplished that the XML file file used but not the real database?
    Or: How can I test a controller method which invokes a service method finally gets an entity via a repository?
    Or can I just test controller calls with no or only primitive code?
    My intention was to test the controller intended to be used finally in production.

    • Petri Oct 23, 2013 @ 23:36

      Hi Dirk,

      If you want to write integration tests which hit the database, you have to initialize the database by using DBUnit. This blog post describes how you can integrate DBUnit with the Spring Test framework. On the other hand, I just noticed that the actual database configuration is not described here.

      Anyway, the process has the following steps:

      1. Create profile specific configuration files and use Maven resources filtering for setting the actual database connection details into your properties file.
      2. Create a separate profile for integration tests.

      When you now run the integration tests by using the correct profile, the tests are using the configuration fetched from the profile specific configuration file. Thus, you can run your integration against an in-memory database or any other database.

      On the other hand, you can also write unit tests and inject service mocks to your controllers. If you want to do this, check out my Spring MVC Test tutorial. That tutorial has three blog posts and videos about the unit testing of Spring MVC controllers.

      • Dirk Oct 25, 2013 @ 11:39

        ...but why can't I use the database used finally (for the time being I just have read operations) using the real controller/service/jpaRepository?

        • Petri Oct 25, 2013 @ 13:20

          DbUnit is used to initialize the used database to a known state before the tests are run. In other words, it reads the data from the XML file and inserts it into the used database. When the tests are run, they use the database which contains the information specified in the data set file.

          Or did you want to write tests against the development database without using DbUnit?

  • Radek May 14, 2014 @ 2:43

    Really nice write up!
    Couple of changes for the current version:
    - the test class needs @WebAppConfiguration annotation, otherwise the @Resource on webApplicationContext will not find the context
    - MockMvcBuilders.webApplicationContextSetup(webApplicationContext).build(); is now:
    MockMvcBuilders.webAppContextSetup(webApplicationContext).build();

    Thanks for the great post!

  • Kent May 16, 2014 @ 23:51

    You should note that the BoneCP jar file is needed.

  • Anonymous Mar 30, 2016 @ 16:31

    mockMvc = MockMvcBuilders.webAppContextSetup(this.webAppContext).build(); is returning null for me

    • Petri Mar 30, 2016 @ 17:37

      I assume that this was related to this comment. Anyway, I hope that I can help you to solve your problem.

  • Anonymous Mar 30, 2016 @ 16:40

    private MockMvc mockMvc;

    @Autowired
    private WebApplicationContext webAppContext;

    @Before
    public void setUp() {
    mockMvc = MockMvcBuilders.webAppContextSetup(webAppContext)
    .build();
    }
    IllegalArgument WebApplicationContext is require

    • Petri Mar 30, 2016 @ 17:28

      You can solve this by annotating your test class with the @WebAppConfiguration annotation. The examples of this blog post use a really old Spring MVC Test version and some of them are out of date (especially the configuration examples). This blog post describes how you can migrate the configuration to use Spring MVC Test 3.2 (or newer).

  • Navin Murrari Jun 9, 2016 @ 9:47

    Nice Tutorials as usual great!

    • Petri Jun 13, 2016 @ 13:45

      Thank you for your kind words. I really appreciate them.

  • Xava Sep 5, 2017 @ 14:35

    Hi Petri,
    thanks for the great tutorial. Can you help me with the issue i am facing. I have the test code as following:

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(loader = WebContextLoader.class, classes = {ApplicationContext.class})
    @TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
            DirtiesContextTestExecutionListener.class,
            TransactionalTestExecutionListener.class,
            DbUnitTestExecutionListener.class
    })
    @DatabaseSetup("userData.xml")
    @WebAppConfiguration
    public class UserControllerIntegrationTest {
        @Resource
        private WebApplicationContext webApplicationContext;
    
        private MockMvc mockMvc;
    
        @Before
        public void setUp() {
            mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
        }
    
        @Test
        @ExpectedDatabase("userData.xml")
        public void findAll_UsersFound_ShouldReturnFoundUserEntries() throws Exception {
            mockMvc.perform(get("/users")).andDo(MockMvcResultHandlers.print())
                    .andExpect(status().isOk())
                    .andExpect(content().contentType(TestUtil.APPLICATION_JSON_UTF8));
    
        }
    }
    


    It gives the error as:: org.springframework.web.servlet.PageNotFound - No mapping found for HTTP request with URI [/users]
    Am i doing anything wrong. I followed aloong your tutorial from part 1. And where should we keeb the userData.xml file?? If i add a WebappContext.xml and testcontext.xml in the ContextContigutation annotation as we have in your unit testing tutorials then it would work but then again it would not use the test database. It would return the null values from the service.

    • Petri Sep 7, 2017 @ 8:42

      Hi,

      It gives the error as:: org.springframework.web.servlet.PageNotFound – No mapping found for HTTP request with URI[/users]

      It seems that the Spring MVC Test framework cannot find the controller method that should process your request.

      Which Spring version are you using? The reason why I ask this is that this tutorial is very old and some posts use Spring 3.1 which requires a totally different configuration than the newer Spring versions. For example, you don't have to specify the value of the loader attribute when you configure the application context configuration class that configures the application context of your integration tests. Also, is the ApplicationContext class your class or the interface provided by Spring Framework?

      And where should we keeb the userData.xml file

      I put my data set files to the src/integration-test/resources directory. You can either put them to the same package as your test class or use a different package. Typically I follow these two rules:

      • If I use the data set when I initialize the database into a known state and it can be shared by multiple integration tests, I put my data sets to a "common" package.
      • If the data set writes assertions to the data found from the database or it contains test specific customizations to the test data, I will put it to the same package as my test class.

      In your case, the data set file should be in the same package as your test class because the @DatabaseSetup annotation doesn't configure the package of the data set.

Leave a Reply