Get 50% discount from my Spring Data book! Use campaign code BIG50. The deal lasts only 4 days so be fast!

Unit Testing of Spring MVC Controllers: “Normal” Controllers

Share
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. Create a new TodoDTO object by using our test data builder. Set the title and the description of the object.
  4. Execute a POST request to url ‘/todo/add’. Set the content type of the request to ‘application/x-www-form-urlencoded’. Ensure that the content of our form object is send in the body of the request. Set the form object into session.
  5. Verify that the HTTP status code 200 is returned.
  6. Verify that the name of the returned view is ‘todo/add’.
  7. Verify that the request is forwarded to url ‘/WEB-INF/jsp/todo/add.jsp’.
  8. Verify that our model attribute has field errors in the title and description fields.
  9. Ensure that the id of our model attribute is null.
  10. Ensure that the description of our model attribute is correct.
  11. Ensure that the title of our model attribute is correct.
  12. 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);

        TodoDTO formObject =  new TodoDTOBuilder()
                .description(description)
                .title(title)
                .build();

        mockMvc.perform(post("/todo/add")
                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                .content(TestUtil.convertObjectToFormUrlEncodedBytes(formObject))
                .sessionAttr("todo", formObject)
        )
                .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 some static methods of the TestUtil class. These methods are described in the following:

  • The createStringWithLength(int length) method creates a new String object with the given length and returns the created object.
  • The convertObjectToFormUrlEncodedBytes(Object object) method converts the object into form url encoded String object and returns the content of that String object as a byte array.

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 byte[] convertObjectToFormUrlEncodedBytes(Object object) {
        ObjectMapper mapper = new ObjectMapper();
        mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);

        Map<String, Object> propertyValues = mapper.convertValue(object, Map.class);

        Set<String> propertyNames = propertyValues.keySet();
        Iterator<String> nameIter = propertyNames.iterator();

        StringBuilder formUrlEncoded = new StringBuilder();

        for (int index=0; index < propertyNames.size(); index++) {
            String currentKey = nameIter.next();
            Object currentValue = propertyValues.get(currentKey);

            formUrlEncoded.append(currentKey);
            formUrlEncoded.append("=");
            formUrlEncoded.append(currentValue);

            if (nameIter.hasNext()) {
                formUrlEncoded.append("&");
            }
        }

        return formUrlEncoded.toString().getBytes();
    }

    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 form object by using the test data builder class. Set “legal” values to the title and description fields of the created object.
  2. Create a Todo object which is returned when the add() method of the TodoService interface is called.
  3. Configure our mock object to return the created Todo object when its add() method is called and the created form object is given as a method parameter.
  4. Execute a POST request to url ‘/todo/add’. Set the content type of the request to ‘application/x-www-form-urlencoded’. Ensure that the content of our form object is send in the body of the request. Set the form object into session.
  5. Verify that the HTTP status code 302 is returned.
  6. Ensure that the name of the returned view is ‘redirect:todo/{id}’.
  7. Ensure that the request is redirected to url ‘/todo/1′.
  8. Verify that the model attribute called id is ’1′.
  9. Verify that the feedback message is set.
  10. Verify that the add() method of our mock object is called only once and that the form object was given as a method parameter.
  11. Verify that no other methods of the mock object were 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.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.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 {
        TodoDTO formObject = new TodoDTOBuilder()
                .description("description")
                .title("title")
                .build();

        Todo added = new TodoBuilder()
                .id(1L)
                .description(formObject.getDescription())
                .title(formObject.getTitle())
                .build();

        when(todoServiceMock.add(formObject)).thenReturn(added);

        mockMvc.perform(post("/todo/add")
                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                .content(TestUtil.convertObjectToFormUrlEncodedBytes(formObject))
                .sessionAttr("todo", formObject)
        )
                .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.")));

        verify(todoServiceMock, times(1)).add(formObject);
        verifyNoMoreInteractions(todoServiceMock);
    }
}

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 renders a view.
  • We learned to write unit tests for controller methods which handles 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 enjoy reading similar content, you should follow me on Twitter:

About the Author

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

About Petri Kainulainen →

23 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

Leave a Comment