Enterprise Java

Unit Testing of Spring MVC Controllers: “Normal” Controllers

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.

Note: 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 MockMvcResultMathers 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 gets the todo entries by calling the findAll() method of the TodoService interface. This method returns a list of Todo objects.
  2. It adds the received list to the model.
  3. 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 forward 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 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 TodoNotEntryNotFoundException.
  2. It adds the found todo entry to the model.
  3. 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 TodoEntryNotFoundException 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 TodoEntryNotFoundException 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 tests 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 a 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 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 a correct method parameter (1L).
  11. Ensure and 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 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.
  2. 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.
  3. It creates the feedback message about the added todo entry and adds the message to the RedirectAttributes object given as a method parameter.
  4. It adds the id of the added todo entry to the RedirectAttributes object.
  5. 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.
 

Petri Kainulainen

Petri 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.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button