Do want to get a better understanding of Spring web application architecture? If so, get started right now!

Unit Testing of Spring MVC Controllers: “Normal” Controllers

A treasure chest

The first part of this tutorial described how we can configure our unit tests which use the Spring MVC Test framework.

Now it is time to get our hands dirty and learn how we can write unit tests for “normal” controllers.

The obvious next question is

What is a normal controller?

Well, a normal controller (in the context of this blog post) is a controller which either renders a view or handles form submissions.

Let’s get started.

I recommend that you read the first part of this tutorial before reading this blog post (If you have already read it, you are allowed to continue reading)

Getting The Required Dependencies with Maven

We can get the required testing dependencies by adding the following dependency declarations to the POM file of our example application:

  • Jackson 2.2.1 (core and databind modules). We use Jackson to transform objects into url encoded String objects.
  • Hamcrest 1.3. We use Hamcrest matchers when we are writing assertions for the responses.
  • JUnit 4.11 (exclude the hamcrest-core dependency).
  • Mockito 1.9.5
  • Spring Test 3.2.3.RELEASE

The relevant part of our pom.xml file looks as follows:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.2.1</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.2.1</version>
    <scope>test</scope>
</dependency>
<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.11</version>
    <scope>test</scope>
    <exclusions>
        <exclusion>
            <artifactId>hamcrest-core</artifactId>
            <groupId>org.hamcrest</groupId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>1.9.5</version>
    <scope>test</scope>
</dependency>
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-test</artifactId>
	<version>3.2.3.RELEASE</version>
	<scope>test</scope>
</dependency>

Let’s move on and find out how we can write unit tests for Spring MVC controllers by using the Spring MVC Test framework.

Writing Unit Tests for Controller Methods

Every unit test which we write to test the behavior of a controller method consists of these steps:

  1. We send a request to the tested controller method.
  2. We verify that we received the expected response.

The Spring MVC Test framework has a few “core” classes which we can use for implementing these steps in our tests. These classes are described in the following:

  • We can build our requests by using the static methods of the MockMvcRequestBuilders class. Or to be more specific, we can create request builders which are then passed as a method parameter to the method which executes the actual request.
  • The MockMvc class is the main entry point of our tests. We can execute requests by calling its perform(RequestBuilder requestBuilder) method.
  • We can write assertions for the received response by using the static methods of the MockMvcResultMatchers class.

Next we will take a look at some examples which demonstrates how we can use these classes in our unit tests. We will write unit tests for the following controller methods:

  • The first controller method renders a page which shows a list of todo entries.
  • The second controller method renders a page which shows the information of a single todo entry.
  • The third controller method handles form submissions of the form which is used to add new todo entries to the database.

Rendering The Todo Entry List Page

Let’s start by taking a look at the implementation of the controller method which is used to render the todo entry list page.

Excpected Behavior

The implementation of the controller method which is used to show the information of all todo entries has the following steps:

  1. It processes GET requests send to url ‘/’.
  2. It gets the todo entries by calling the findAll() method of the TodoService interface. This method returns a list of Todo objects.
  3. It adds the received list to the model.
  4. It returns the name of the rendered view.

The relevant part of the TodoController class looks as follows:

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import java.util.List;

@Controller
public class TodoController {

    private final TodoService service;
    
    @RequestMapping(value = "/", method = RequestMethod.GET)
    public String findAll(Model model) {
        List<Todo> models = service.findAll();
        model.addAttribute("todos", models);
        return "todo/list";
    }
}

We are now ready to write an unit test for this method. Let’s see how we can do it.

Test: Todo Entries Are Found

We can write an unit test for this controller method by following steps:

  1. Create the test data which is returned when our service method is called. We use a concept called test data builder when we are creating the test data for our test.
  2. Configure the used mock object to return the created test data when its findAll() method is called.
  3. Execute a GET request to url ‘/’.
  4. Ensure that the HTTP status code 200 is returned.
  5. Ensure that the name of the returned view is ‘todo/list’.
  6. Ensure that the request is forwarded to url ‘/WEB-INF/jsp/todo/list.jsp’.
  7. Ensure that model attribute called todos has two items in it.
  8. Ensure that the model attribute called todos contains the correct items.
  9. Verify that the findAll() method of our mock object was called only once.
  10. Ensure that other methods of the mock object were not called during the test.

The source code of our unit test looks as follows:

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
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.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import java.util.Arrays;

import static org.hamcrest.Matchers.*;
import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {TestContext.class, WebAppContext.class})
@WebAppConfiguration
public class TodoControllerTest {

    private MockMvc mockMvc;

    @Autowired
    private TodoService todoServiceMock;

    //Add WebApplicationContext field here

    //The setUp() method is omitted.

    @Test
    public void findAll_ShouldAddTodoEntriesToModelAndRenderTodoListView() throws Exception {
        Todo first = new TodoBuilder()
                .id(1L)
                .description("Lorem ipsum")
                .title("Foo")
                .build();

        Todo second = new TodoBuilder()
                .id(2L)
                .description("Lorem ipsum")
                .title("Bar")
                .build();

        when(todoServiceMock.findAll()).thenReturn(Arrays.asList(first, second));

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

        verify(todoServiceMock, times(1)).findAll();
        verifyNoMoreInteractions(todoServiceMock);
    }
}

Rendering The View Todo Entry Page

Before we can write the actual unit tests for our controller method, we have to take a closer look at the implementation of that method.

Let’s move on and find out how our controller is implemented.

Expected Behavior

The controller method which is used to show the information of a single todo entry is implemented by following these steps:

  1. It processes GET requests send to url ‘/todo/{id}’. The {id} is a path variable which contains the id of the requested todo entry.
  2. It obtains the requested todo entry by calling the findById() method of the TodoService interface and passes the id of the requested todo entry as a method parameter. This method returns the found todo entry. If no todo entry is found, this method throws a TodoNotFoundException.
  3. It adds the found todo entry to the model.
  4. It returns the name of the rendered view.

The source code of our controller method looks as follows:

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

@Controller
public class TodoController {

    private final TodoService service;

    @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";
    }
}

Our next question is:

What happens when a TodoNotFoundException is thrown?

In the previous part of this tutorial, we created an exception resolver bean which is used to handle exceptions thrown by our controller classes. The configuration of this bean looks as follows:

@Bean
public SimpleMappingExceptionResolver exceptionResolver() {
	SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();

	Properties exceptionMappings = new Properties();

	exceptionMappings.put("net.petrikainulainen.spring.testmvc.todo.exception.TodoNotFoundException", "error/404");
	exceptionMappings.put("java.lang.Exception", "error/error");
	exceptionMappings.put("java.lang.RuntimeException", "error/error");

	exceptionResolver.setExceptionMappings(exceptionMappings);

	Properties statusCodes = new Properties();

	statusCodes.put("error/404", "404");
	statusCodes.put("error/error", "500");

	exceptionResolver.setStatusCodes(statusCodes);

	return exceptionResolver;
}

As we can see, if a TodoNotFoundException is thrown, our application renders the ‘error/404′ view and returns the HTTP status code 404.

It is clear that we have to write two tests for this controller method:

  1. We have to write a test which ensures that our application is working correctly when the todo entry is not found.
  2. We have to write a test which verifies that our application is working correctly when the todo entry is found.

Let’s see how we can write these tests.

Test 1: Todo Entry Is Not Found

First, we must ensure that our application is working property when the requested todo entry is not found. We can write the test which ensures this by following these steps:

  1. Configure the mock object to throw a TodoNotFoundException when its findById() method is called and the id of the requested todo entry is 1L.
  2. Execute a GET request to url ‘/todo/1′.
  3. Verify that the HTTP status code 404 is returned.
  4. Ensure that the name of the returned view is ‘error/404′.
  5. Ensure that the request is forwarded to url ‘/WEB-INF/jsp/error/404.jsp’.
  6. Verify that the findById() method of the TodoService interface is called only once with the correct method parameter (1L).
  7. Verify that no other methods of the mock object were called during this test.

The source code of our unit test looks as follows:

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
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.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {TestContext.class, WebAppContext.class})
@WebAppConfiguration
public class TodoControllerTest {

    private MockMvc mockMvc;

    @Autowired
    private TodoService todoServiceMock;

    //Add WebApplicationContext field here

    //The setUp() method is omitted.

    @Test
    public void findById_TodoEntryNotFound_ShouldRender404View() throws Exception {
        when(todoServiceMock.findById(1L)).thenThrow(new TodoNotFoundException(""));

        mockMvc.perform(get("/todo/{id}", 1L))
                .andExpect(status().isNotFound())
                .andExpect(view().name("error/404"))
                .andExpect(forwardedUrl("/WEB-INF/jsp/error/404.jsp"));

        verify(todoServiceMock, times(1)).findById(1L);
        verifyZeroInteractions(todoServiceMock);
    }
}

Test 2: Todo Entry Is Found

Second, we must write a test which ensures that our controller is working properly when a todo entry is found. We can do this by following these steps:

  1. Create the Todo object which is returned when our service method is called. Again, we create the returned Todo object by using our test data builder.
  2. Configure our mock object to return the created Todo object when its findById() method is called by using a method parameter 1L.
  3. Execute a GET request to url ‘/todo/1′.
  4. Verify that the HTTP status code 200 is returned.
  5. Ensure that the name of the returned view is ‘todo/view’.
  6. Ensure that the request is forwarded to url ‘/WEB-INF/jsp/todo/view.jsp’.
  7. Verify that the id of the model object called todo is 1L.
  8. Verify that the description of the model object called todo is ‘Lorem ipsum’.
  9. Verify that the title of the model object called todo is ‘Foo’.
  10. Ensure that the findById() method of our mock object is called only once with the correct method parameter (1L).
  11. Ensure that the other methods of the mock object were not called during our test.

The source code of our unit test looks as follows:

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
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.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import static org.hamcrest.Matchers.hasProperty;
import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {TestContext.class, WebAppContext.class})
@WebAppConfiguration
public class TodoControllerTest {

    private MockMvc mockMvc;

    @Autowired
    private TodoService todoServiceMock;

    //Add WebApplicationContext field here

    //The setUp() method is omitted.

    @Test
    public void findById_TodoEntryFound_ShouldAddTodoEntryToModelAndRenderViewTodoEntryView() throws Exception {
        Todo found = new TodoBuilder()
                .id(1L)
                .description("Lorem ipsum")
                .title("Foo")
                .build();

        when(todoServiceMock.findById(1L)).thenReturn(found);

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

        verify(todoServiceMock, times(1)).findById(1L);
        verifyNoMoreInteractions(todoServiceMock);
    }
}

Handling The Form Submission of The Add Todo Entry Form

Again, we will first take a look at the expected behavior of our controller method before we will write the unit tests for it.

Expected Behavior

The controller method which handles the form submissions of the add todo entry form is implemented by following these steps:

  1. It processes POST requests send to url ‘/todo/add’.
  2. It checks that the BindingResult object given as a method parameter doesn’t have any errors. If errors are found, it returns the name of the form view.
  3. It adds a new Todo entry by calling the add() method of the TodoService interface and passes the form object as a method parameter. This method creates a new todo entry and returns it.
  4. It creates the feedback message about the added todo entry and adds the message to the RedirectAttributes object given as a method parameter.
  5. It adds the id of the added todo entry to the RedirectAttributes object.
  6. It returns the name of a redirect view which redirects the request to the view todo entry page.

The relevant part 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.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import javax.validation.Valid;
import java.util.Locale;

@Controller
@SessionAttributes("todo")
public class TodoController {

    private final TodoService service;

    private final MessageSource messageSource;

    @RequestMapping(value = "/todo/add", method = RequestMethod.POST)
    public String add(@Valid @ModelAttribute("todo") TodoDTO dto, BindingResult result, RedirectAttributes attributes) {
        if (result.hasErrors()) {
            return "todo/add";
        }

        Todo added = service.add(dto);

        addFeedbackMessage(attributes, "feedback.message.todo.added", added.getTitle());
        attributes.addAttribute("id", added.getId());

        return createRedirectViewPath("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();
    }
}

As we can see, the controller method uses a TodoDTO object as a form object. The TodoDTO class is a simple DTO class which source code looks as follows:

import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.NotEmpty;

public class TodoDTO {

    private Long id;

    @Length(max = 500)
    private String description;

    @NotEmpty
    @Length(max = 100)
    private String title;

	//Constructor and other methods are omitted.
}

The TodoDTO class declares some validation constraints which are described in following:

  • The title of a todo entry cannot be empty.
  • The maximum length of the description is 500 characters.
  • The maximum length of the title is 100 characters.

If we think about the tests which we should write for this controller method, it is clear that we must ensure that

  1. The controller method is working property when the validation fails.
  2. The controller method is working property when a todo entry is added to the database.

Let’s find out how we can write these tests.

Test 1: Validation Fails

First, we have to write a test which ensures that our controller method is working properly when the validation fails. We can write this test by following these steps:

  1. Create a title which has 101 characters.
  2. Create a description which has 501 characters.
  3. Execute a POST request to url ‘/todo/add’ by following these steps:
    1. Set the content type of the request to ‘application/x-www-form-urlencoded’.
    2. Send the description and title of the todo entry as request parameters.
    3. Set a new TodoDTO object to session. This is required because our controller is annotated with the @SessionAttributes annotation.
  4. Verify that the HTTP status code 200 is returned.
  5. Verify that the name of the returned view is ‘todo/add’.
  6. Verify that the request is forwarded to url ‘/WEB-INF/jsp/todo/add.jsp’.
  7. Verify that our model attribute has field errors in the title and description fields.
  8. Ensure that the id of our model attribute is null.
  9. Ensure that the description of our model attribute is correct.
  10. Ensure that the title of our model attribute is correct.
  11. Ensure that the methods of our mock object were not called during the test.

The source code of our unit test looks as follows:

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
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.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import static org.hamcrest.Matchers.hasProperty;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {TestContext.class, WebAppContext.class})
@WebAppConfiguration
public class TodoControllerTest {

    private MockMvc mockMvc;

    @Autowired
    private TodoService todoServiceMock;

    //Add WebApplicationContext field here

    //The setUp() method is omitted.

    @Test
    public void add_DescriptionAndTitleAreTooLong_ShouldRenderFormViewAndReturnValidationErrorsForTitleAndDescription() throws Exception {
        String title = TestUtil.createStringWithLength(101);
        String description = TestUtil.createStringWithLength(501);

        mockMvc.perform(post("/todo/add")
                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                .param("description", description)
                .param("title", title)
                .sessionAttr("todo", new TodoDTO())
        )
                .andExpect(status().isOk())
                .andExpect(view().name("todo/add"))
                .andExpect(forwardedUrl("/WEB-INF/jsp/todo/add.jsp"))
                .andExpect(model().attributeHasFieldErrors("todo", "title"))
                .andExpect(model().attributeHasFieldErrors("todo", "description"))
                .andExpect(model().attribute("todo", hasProperty("id", nullValue())))
                .andExpect(model().attribute("todo", hasProperty("description", is(description))))
                .andExpect(model().attribute("todo", hasProperty("title", is(title))));

        verifyZeroInteractions(todoServiceMock);
    }
}

Our test case calls the static createStringWithLength(int length) method of the TestUtil class. This method creates a new String object with the given length and returns the created object.

The source code of the TestUtil class looks as follows:

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public class TestUtil {

    public static String createStringWithLength(int length) {
        StringBuilder builder = new StringBuilder();

        for (int index = 0; index < length; index++) {
            builder.append("a");
        }

        return builder.toString();
    }
}

Test 2: Todo Entry Is Added to The Database

Second, we have to write a test which ensures that our controller is working properly when a new todo entry is added to the database. We can write this test by following these steps:

  1. Create a Todo object which is returned when the add() method of the TodoService interface is called.
  2. Configure our mock object to return the created Todo object when its add() method is called a TodoDTO object is given as a method parameter.
  3. Execute a POST request to url ‘/todo/add’ by following these steps:
    1. Set the content type of the request to ‘application/x-www-form-urlencoded’.
    2. Send the description and title of the todo entry as request parameters.
    3. Set a new TodoDTO object to session. This is required because our controller is annotated with the @SessionAttributes annotation.
  4. Verify that the HTTP status code 302 is returned.
  5. Ensure that the name of the returned view is ‘redirect:todo/{id}’.
  6. Ensure that the request is redirected to url ‘/todo/1′.
  7. Verify that the model attribute called id is ‘1’.
  8. Verify that the feedback message is set.
  9. Verify that the add() method of our mock object is called only once and that a TodoDTO object was given as a method parameter. Capture the object given as a method parameter by using an ArgumentCaptor object.
  10. Verify that no other methods of the mock object were called during our test.
  11. Verify that the field values of the TodoDTO object are correct.

The source code of our unit test looks as follows:

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
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.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {TestContext.class, WebAppContext.class})
@WebAppConfiguration
public class TodoControllerTest {

    private MockMvc mockMvc;

    @Autowired
    private TodoService todoServiceMock;

    //Add WebApplicationContext field here

    //The setUp() method is omitted.

    @Test
    public void add_NewTodoEntry_ShouldAddTodoEntryAndRenderViewTodoEntryView() throws Exception {
        Todo added = new TodoBuilder()
                .id(1L)
                .description("description")
                .title("title")
                .build();

        when(todoServiceMock.add(isA(TodoDTO.class))).thenReturn(added);

        mockMvc.perform(post("/todo/add")
                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                .param("description", "description")
                .param("title", "title")
                .sessionAttr("todo", new TodoDTO())
        )
                .andExpect(status().isMovedTemporarily())
                .andExpect(view().name("redirect:todo/{id}"))
                .andExpect(redirectedUrl("/todo/1"))
                .andExpect(model().attribute("id", is("1")))
                .andExpect(flash().attribute("feedbackMessage", is("Todo entry: title was added.")));

		ArgumentCaptor<TodoDTO> formObjectArgument = ArgumentCaptor.forClass(TodoDTO.class);
		verify(todoServiceMock, times(1)).add(formObjectArgument.capture());
		verifyNoMoreInteractions(todoServiceMock);

		TodoDTO formObject = formObjectArgument.getValue();

		assertThat(formObject.getDescription(), is("description"));
		assertNull(formObject.getId());
		assertThat(formObject.getTitle(), is("title"));
    }
}

Summary

We have now written some unit tests for “normal” controller methods by using the Spring MVC Test framework. This tutorial has taught has four things:

  • We learned to create requests which are processed by the tested controller methods.
  • We learned to write assertions for the responses returned by the tested controller methods.
  • We learned how we can write unit tests for controller methods which render a view.
  • We learned to write unit tests for controller methods which handle form submissions.

The next part of this tutorial describes how we can write unit tests for a REST API.

P.S. The example application of this blog post is available at Github. I recommend that you check it out because it has some unit tests which were not covered in this blog post.

If you want to learn more about Spring MVC Test, you should read my Spring MVC Test tutorial.

About the Author

Petri Kainulainen is passionate about software development and continuous improvement. He is specialized in software development with the Spring Framework and is the author of Spring Data book.

About Petri Kainulainen →

53 comments… add one

  • Its really helpful. Please consider to give as eclipse project. It will be very useful for a lot those don’t use maven like me

    Reply
    • Thank you for your comment.

      Unfortunately I don’t use Eclipse (I switched to IntelliJ Idea a few years ago).

      However, you can import Maven projects to Eclipse by using the Eclipse Maven plugin (m2e). Check out this blog post for more details about this.

      I hope that this helps you to get the project up and running!

      Reply
  • thank you very much

    Reply
    • You are welcome!

      Reply
  • thanks for your post , but i have a question i have i form with at least 150 field, is there another ways to make the test more simple ?
    because when i try your ways its really works, but its very complicated :(
    thanks :D

    Reply
    • Unfortunately I have to say that I am not aware of a way which would make testing easier if you have very large forms. Is it possible to split that form into smaller forms?

      Reply
      • I think my form not possible to split because its one model.. :(
        hy petri what the best tools according to you which used for test presentation layer(view) ?
        thanks very much petri :D

        Reply
  • I am getting NullPointerException during this line

    mockMvc.perform(get(“/”)).

    Am I missing any thing?

    Reply
  • need those methods’API

    Reply
  • Check out the Javadocs of the these classes:

    Reply
  • Very nice article!

    Thank you!

    Reply
    • You are welcome! I am happy to hear that you like this blog post.

      Reply
  • Great article. However when I tried start the server (clean jetty:run), I received a message:

    2013-12-13 21:39:57.502:INFO:oejs.Server:jetty-8.1.5.v20120716
    2013-12-13 21:39:58.488:INFO:oejpw.PlusConfiguration:No Transaction manager found – if your webapp requires one, please configure one.
    2013-12-13 21:40:04.835:INFO:/:No Spring WebApplicationInitializer types detected on classpath

    2013-12-13 21:40:05.318:INFO:oejs.AbstractConnector:Started SelectChannelConnector@0.0.0.0:8080

    and I saw content of directory / (WEB-INF, static).

    Is there something I’m doing wrong?

    Reply
    • Did you clone the example project from Github or did you try to create one from the scratch by following my instructions?

      The log message which you should be interested in this is this:

      2013-12-13 21:40:04.835:INFO:/:No Spring WebApplicationInitializer types detected on classpath

      It means that Jetty did not find the configuration class of the web application (click the link to see the configuration class of the example application). The Spring reference manual has also more information about this.

      I hope that I could answer to your question.

      Reply
  • Very Nice and great article covering almost every thing.

    Reply
    • Thank you for your kind words.

      These tutorials cover almost everything but one thing that is missing is writing unit tests for file uploads. Do you think that this would be an useful addition to these tutorials?

      Reply
  • Awesome write up … keep em coming…

    Reply
    • Thank you! I will.

      Reply
  • Thanks for your great tutorial about testing in Spring Platform.
    I am a newbie in testing of Spring MVC. I’m a bit confused about integration tests and unit tests.
    I downloaded the source codes from Github. Which project gives a complete guide for testing?
    For example, “controllers” project tests controller activities, controller-unittest also test controller activities.
    But Spring-32 project makes testing of routing inside the integration-test, unit-test side does not have a routing test.
    As I mentioned earlier, the previous projects have routing tests in both sides unit-test and integration-test.

    Reply
    • I will try to clarify things a bit:

      I wrote most of the integration testing tutorials before Spring Framework 3.2 was released. These tutorials use a standalone library called spring-test-mvc. When Spring 3.2 was released, the code of this library was included in the Spring Test Framework.

      In other words, if you want to simply read the source code, you should check out the example applications of my unit testing tutorials. These applications use the Spring MVC Test framework in both unit and integration tests.

      I hope that this answered to your question. If not, feel free to answer to my comment. :)

      Reply
  • this is the best post that i found!

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

      Reply
  • Hi Petri! Thank you for these very useful tutorial!

    I’m trying to test my controller in the way you explain in this tutorial (like add_DescriptionAndTitleAreTooLong_ShouldRenderFormViewAndReturnValidationErrorsForTitleAndDescription), but when i post() content() data to my controller, handler method is triggered and the backing object is not populated.. all properties are null.

    Without lose your time.. do you know which could be the problem? :)

    Thank you very much!! I’m waiting for your new testing book!! I hope goes on sale soon!

    Reply
    • Hi Marco,

      There are two possible reasons for this:

      1. Your controller class isn’t annotated with the @SessionAttributes annotation.
      2. You didn’t put the form object to session when you invoke the handler method in your test.

      The relevant part of the TodoController class looks as follows:

      
      @Controller
      @SessionAttributes("todo")
      public class TodoController {
      }
      
      

      I use the @SessionAttributes annotation to ensure that my form object is saved to a session. This makes it possible to display the “old” field values if validation fails.

      I can now set the form object to session in my test by using the sessionAttr() method as follows:

      
       mockMvc.perform(post("/todo/add")
      		.contentType(MediaType.APPLICATION_FORM_URLENCODED)
      		.content(TestUtil.convertObjectToFormUrlEncodedBytes(formObject))
      		.sessionAttr("todo", formObject)
      )
      
      

      I hope that this answered to your question. :)

      Reply
      • You have solved my problem! :) Thank you!!

        So the @SessionAttributes annotation in controller is ONLY for testing purpose?

        Reply
        • You are welcome!

          The @SessionAttributes annotation is used to save your form object to “conversational session”. In other words, you can ensure that your form object is available as long as the form is submitted successfully. That is why the value of the @SessionAttributes annotation and the name of your form object should be equal.

          There is an easy to way to see what it does:

          • Submit a form which has validation errors (add something to form fields) when your controller class is not annotated with the @SessionAttributes annotation.
          • Submit a form which has validation errors (add something to form fields) when your controller class is annotated with the @SessionAttributes annotation.

          See the difference?

          Reply
          • No :) But I think that it’s because I have in my controller

            if (bindingResult.hasErrors()) {
            uiModel.addAttribute(“message”, new Message(“error”, messageSource.getMessage(“entry_save_fail”, new Object[]{}, locale)));
            uiModel.addAttribute(“language”, language);
            DateTimeUtil.dateTimePattern(messageSource, uiModel, locale);
            return “language/create”;
            }

            I think that the difference could be that I can avoid to re-add to uiModel Map language object.. could it be?

          • Actually when I wrote that question I didn’t remember that the difference might not be obvious unless you use a debugger (this depends from the form). Sorry about that.

            Check out this Stack Overflow answer. It explains the difference between those two situations.

          • Thank you. I get it!

            I have tryed to remove an input type tied to a non null property from my update form and:
            1) with @SessionAttributes after submit update form the relative property is not null (it reuse the same instance of the object used to create update form and populate values with form properties).
            2)without @SessionAttributes the property is null (a new instance is created and populated with form properties).

            So this can be for example a valid method to avoid hidden input in forms?

            Another question is: in this test the following line:

            .content(TestUtil.convertObjectToFormUrlEncodedBytes(formObject))

            could be removed.. no?

            Sorry if I’m flooding your blog with my questions :) Tell me and I’ll stop immediately!

          • Don’t worry about flooding my blog. If I wouldn’t want to answer questions, I wouldn’t allow people to leave them. :)

            I think that this Stack Overflow answer provides a good practical example about the benefits of the @SessionAttributes annotation.

            About your question: You are right. You can remove that line. However, you can remove it only because the session attribute already contains the updated properties. This is not case when the form is really updated. In that case, the session attribute contains the “old” values of the form object, and the values send in the request body should replace them. However, when I tried this, it didn’t work.

            Good work. You found a bug from my tests! I have to investigate what is the correct way to test this.

            Update: I found it. I must update the example application (and this blog post). Again, thanks for noticing this!

          • I’m glad to be helpful!

            One last thing. I’ve never used @SessionAttributes approach before. Searching on internet I’ve found that it suffers, or suffered (https://jira.spring.io/browse/SPR-4160 not seems to be resolved) from a problem in tabbed browsing.

            What do you think about it?

            Thanks again!!

          • I think that this kind of “sucks”. However, I would still listen Juergen Hoeller and use the Spring Web Flow if I would have to support this.

  • Perti,

    Thanks a lot for the detailed post !!!!!!!
    I am going to implement the same for my controllers and get back with comments/questions…
    Really use full blog.

    Reply
    • Thank you for your kind words. I really appreciate them. Also, I am waiting for your comments / questions.

      Reply
  • In your add_DescriptionAndTitleAreTooLong_ShouldRenderFormViewAndReturnValidationErrorsForTitleAndDescription() on this page you use “.sessionAttr(“todo”, formObject)”, but formObject doesn’t seem to exist. On github this is replaced with “.sessionAttr(“todo”, new TodoDTO())”, so there’s no problem there.

    Reply
    • Thanks!

      I updated this blog post some time ago and probably forgot to change that code example. Anyway, it is fixed now.

      Thanks again!

      Reply
  • I am want to mock “public void claimTask(String taskId, String userId);” this method which i am calling in my Rest web sevice how do i do it ?
    I am not getting the ouput

    
    @Test
    public void testClaimSuccess() throws Exception{
    		 
    	doNothing()
    	.doThrow(new RuntimeException("t1"))
    	.when(cmmnEngineRuntimeService).claimTask("t1", "u1");	
    		
    	this.mockMvc
    		.perform(MockMvcRequestBuilders.post(
    				"/cmmncase/claim/{taskId}","taskId")
    		)
    		.andExpect(status().isOk())
    		.andExpect(redirectedUrl("claim/t1"))
    		.andExpect(model().size(1))
    		.andExpect(model().attributeExists("claim"))
    		.andExpect(flash().attributeCount(1))
    		.andExpect(flash().attribute(
    				"You have Claim The Task", 
    				"success"
    		));		                  
    }
    
    
    Reply
    • If you want that the void claimTask(String taskId, String userId) method throws a RuntimeException when it is called by using method parameters "t1" and "u1", you should mock it like this:

      
      doThrow(new RuntimeException("foo"))
      	.when(cmmnEngineRuntimeService)
      	.claimTask("t1", "u1");
      
      

      The Javadoc of the Mockito class’ doThrow() method provides more information about this.

      Reply
  • Heyy buddy.. i want to Write Junit test for this Controller.. please help me Out..!

    @Controller
    @RequestMapping(“/login”)
    public class LoginController {
    private LoginUser loginUser;
    private Logger logger=null;
    public LoginController() {
    logger=Logger.getRootLogger();
    }
    @RequestMapping(method=RequestMethod.GET)
    public String showForm(LoginUser user) {
    logger.info(“User Get Login Page”);
    return “login”;
    }

    @RequestMapping(method = RequestMethod.POST)
    public String processForm(@Valid LoginUser loginUser, BindingResult result) {
    String userName = “Admin”;
    String password = “Admin”;

    if (result.hasErrors()) {
    logger.info(“User Login Page has Errors”);
    return “login”;
    }

    if (!loginUser.getUserName().equals(userName) || !loginUser.getPassword().equals(password)) {
    logger.info(“User is not a Valid User”);
    return “error”;
    }

    logger.info(“User is a Valid User, Get Success Page”);
    return “success”;
    }

    public LoginUser getLoginUser() {
    return loginUser;
    }

    public void setLoginUser(LoginUser loginUser) {
    this.loginUser = loginUser;
    }

    }

    Reply
    • Hey i post my code here but its not display

      Reply
      • The comments of my blog are moderated. This means that comment isn’t published until I approve it. Also, you should see a message if your comment is in moderation queue.

        I am currently on a summer holiday so I will not check my blog all the time. I hope that you understand this.

        Reply
        • Hmm..!

          But please solve my problem as you get some free time..! thanks buddy..! Enjoy Holiday..!

          Reply
          • I answered to your question here.

    • You can get started by reading these blog posts:

      Also, if you want concrete examples, you should take a look a these example applications: normal controllers and REST API.

      Reply
  • I have seen so many of tutorials on-line. Your tutorials stand out for the following reasons :
    a. All best practices of coding, unit-testing and building an almost production-ready code.
    b. Excellent content organisation and to-the-point explanations. [No Fluff Just Stuff ;-) ]
    c. Great support in answering blog reader queries

    Thank you for the continuous sharing of your knowledge

    Reply
    • Thank you for your kind words. I really appreciate them. I promise that I will keep learning new things and sharing these things on my blog. :)

      Reply
  • Hi i tried to write my own unit test for simply controller and i get many excetions, here is my code:
    http://stackoverflow.com/questions/25335343/spring-junit-mockito-unit-test-controller-specified-class-is-an-interface

    Please help me! :) Thanks

    Reply
    • The problem is that you have imported the org.springframework.test.context.TestContext interface. The configuration of my unit tests uses a TestContext class (I admit that I could have named it a bit better to avoid this).

      Check out the first part of this tutorial that explains how can configure your unit tests.

      If you want a quick fix, you should create the TestContext class and use it instead of the TestContext interface. The source code of my TestContext class looks as follows:

      
      import org.mockito.Mockito;
      import org.springframework.context.MessageSource;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.context.support.ResourceBundleMessageSource;
       
      @Configuration
      public class TestContext {
       
          @Bean
          public MessageSource messageSource() {
              ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
       
              messageSource.setBasename("i18n/messages");
              messageSource.setUseCodeAsDefaultMessage(true);
       
              return messageSource;
          }
       
          @Bean
          public TodoService todoService() {
              return Mockito.mock(TodoService.class);
          }
      }
      
      

      You should also rename this class so that you don’t face this problem again.

      By the way, you might want to consider using the standalone configuration because this way you don’t have to configure the required mock objects as beans. The application context based setup becomes painful when you have a lot of mock objects because you have to remember to configure them in your application context configuration class. If you use standalone configuration, you don’t have to worry about this because you can configure the required mock objects in the test class.

      Reply
      • Great Article. I have problem to git clone your source code. What is the ?

        Thanks.

        Reply
        • Reply
          • I tried following in gitbash:

            $ git clone https://github.com/pkainulainen/spring-mvc-test-examples/tree/maste
            r/controllers-unittest
            Cloning into ‘controllers-unittest’…
            fatal: repository ‘https://github.com/pkainulainen/spring-mvc-test-examples/tree
            /master/controllers-unittest/’ not found

            What is my problem?
            Thanks

          • Your repository url is not correct. The link that I provided is a direct link to the source code of the example application. However, you cannot use that url for cloning the repository.

            You can clone the repository by using the following command:

            git clone git@github.com:pkainulainen/spring-mvc-test-examples.git

Leave a Comment