Unit Testing of Spring MVC Controllers: "Normal" Controllers

This blog post is outdated! If you want to learn how you can write unit tests for Spring MVC controllers, you should take a look at my updated Spring MVC Test tutorial. It describes how you can write unit tests for Spring MVC controllers with JUnit 5.

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/{id}");
    }

    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. You can get the example application of this blog post from Github. I recommend that you check it out because it has some unit tests which were not covered in this blog post.

173 comments… add one
  • viswa Aug 13, 2013 @ 18:59

    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

    • Petri Aug 13, 2013 @ 20:24

      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!

  • Rachid Aug 26, 2013 @ 15:39

    thank you very much

    • Petri Aug 26, 2013 @ 15:45

      You are welcome!

  • adil ramdan Sep 30, 2013 @ 6:19

    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

    • Petri Sep 30, 2013 @ 21:19

      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?

      • Adil Ramdan Oct 3, 2013 @ 11:51

        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

  • Chandu Oct 12, 2013 @ 0:47

    I am getting NullPointerException during this line

    mockMvc.perform(get("/")).

    Am I missing any thing?

  • wwww Nov 15, 2013 @ 9:28

    need those methods'API

  • Petri Nov 15, 2013 @ 13:25

    Check out the Javadocs of the these classes:

  • Steve Johann Dec 4, 2013 @ 15:59

    Very nice article!

    Thank you!

    • Petri Dec 4, 2013 @ 19:47

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

  • Andrew Dec 13, 2013 @ 22:47

    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?

    • Petri Dec 14, 2013 @ 0:54

      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.

  • santosh joshi Jan 17, 2014 @ 7:31

    Very Nice and great article covering almost every thing.

    • Petri Jan 20, 2014 @ 17:59

      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?

  • Edward Beckett Jan 18, 2014 @ 22:47

    Awesome write up ... keep em coming...

    • Petri Jan 20, 2014 @ 18:00

      Thank you! I will.

  • bos Jan 22, 2014 @ 12:24

    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.

    • Petri Jan 22, 2014 @ 19:48

      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. :)

  • kazaff Feb 23, 2014 @ 13:27

    this is the best post that i found!

    • Petri Feb 25, 2014 @ 18:18

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

  • Marco Apr 23, 2014 @ 8:15

    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!

    • Petri Kainulainen Apr 23, 2014 @ 9:44

      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. :)

      • Marco Apr 23, 2014 @ 12:41

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

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

        • Petri Apr 23, 2014 @ 12:59

          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?

          • Marco Apr 23, 2014 @ 17:17

            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?

          • Petri Apr 23, 2014 @ 20:20

            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.

          • Marco Apr 24, 2014 @ 9:47

            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!

          • Petri Apr 24, 2014 @ 18:05

            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!

          • Marco Apr 28, 2014 @ 8:55

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

          • Petri Apr 28, 2014 @ 21:03

            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.

  • RB May 12, 2014 @ 21:27

    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.

    • Petri May 12, 2014 @ 21:42

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

  • HerpDerp Jun 18, 2014 @ 16:58

    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.

    • Petri Jun 18, 2014 @ 17:07

      Thanks!

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

      Thanks again!

  • priyanka Jun 23, 2014 @ 15:10

    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"
    		));		                  
    }
    
    
    • Petri Jun 23, 2014 @ 19:08

      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.

  • Nikhil Jun 26, 2014 @ 17:57

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

    }

    • Nikhil Jun 26, 2014 @ 18:02

      Hey i post my code here but its not display

      • Petri Jun 26, 2014 @ 18:20

        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.

        • Nikhil Jun 26, 2014 @ 18:23

          Hmm..!

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

          • Petri Jun 26, 2014 @ 18:31

            I answered to your question here.

    • Petri Jun 26, 2014 @ 18:27

      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.

  • schoudari Aug 7, 2014 @ 7:24

    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

    • Petri Aug 7, 2014 @ 20:26

      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. :)

  • Passarinho Aug 16, 2014 @ 2:19

    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

    • Petri Aug 16, 2014 @ 12:50

      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.

      • Jennifer Oct 1, 2014 @ 5:03

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

        Thanks.

        • Petri Oct 1, 2014 @ 9:22
          • Anonymous Oct 4, 2014 @ 16:31

            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

          • Petri Oct 4, 2014 @ 16:49

            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

          • Jennifer Nov 14, 2014 @ 3:20

            After I typed in "git clone git@github.com:pkainulainen/spring-mvc-test-examples.git" in the gitbash, I get following:

            Cloning into 'spring-mvc-test-examples'...
            The authenticity of host 'github.com (192.30.252.128)' can't be established.
            RSA key fingerprint is 16:27:ac:a5:76:28:2d:36:63:1b:56:4d:eb:df:a6:48.
            Are you sure you want to continue connecting (yes/no)? y
            Please type 'yes' or 'no': yes
            Warning: Permanently added 'github.com,192.30.252.128' (RSA) to the list of know
            n hosts.
            Permission denied (publickey).
            fatal: Could not read from remote repository.

            Please make sure you have the correct access rights
            and the repository exists.

            I guess that your computer is down and will try some other time.

            Thank you very much,

          • Petri Nov 15, 2014 @ 23:32

            Hi Jennifer,

            The repository is not hosted on my own computer. It is hosted on Github, and it seems that the server rejected your connection for some reason. You should read an article titled: Error: Permission denied (publickey). It describes how you can troubleshoot (and hopefully solve) this problem.

          • Jennifer Nov 23, 2014 @ 16:14

            OK. I got it.Thanks a lot for letting me know this.

          • Petri Nov 23, 2014 @ 18:43

            Were you able to get the example applications? If not, could you let me know.

            I can email them to you if you leave me a comment that has your email address in it (I won't publish anyone's email address).

          • Jennifer Apr 9, 2015 @ 5:39

            Thank you very much. I have already got your source code.

  • karthik Nov 13, 2014 @ 21:39

    What if you had a POST like this and the UserrequestListWrapper has a list inside it.How you go about validating the fields inside the list. Should i create a couple of Builders?

    
    public String submitUser(@ModelAttribute("listWrapper") UserRequestListWrapper wrapper,
    						 @ModelAttribute("user") User user, 
    						 Error result) { 
    }
    
    public class UserRequestListWrapper {
    
        private List userRequestList;
    
        public List getUserRequestList() {
            return userRequestList;
        }
    
        public void setUserRequestList(List userRequestList) {
            this.userRequestList = userRequestList;
        }
    }
    
    
  • Reji Dec 9, 2014 @ 17:06

    Hi Petri,
    Great Article. Helped a lot in unit testing web applications.
    But I identified a problem in tests expecting exceptions.

    For example in RepositoryTodoServiceTest,
    the test "deleteById_TodoEntryNotFound_ShouldThrowException" expects TodoNotFoundException.
    The test passes since service.deleteById(ID) throws TodoNotFoundException.

    Now the problem is when I change the next line in this test as follows
    verify(repositoryMock, times(5)).findOne(ID); //Expect the findOne(ID) called FIVE times.
    the test execution result was GREEN instead of RED.

    Now I changed the test as follows

    @Test
    public void deleteById_TodoEntryNotFound_ShouldThrowException() throws TodoNotFoundException {
    when(repositoryMock.findOne(ID)).thenReturn(null);

    try {
    service.deleteById(ID);
    fail("TodoNotFoundException not thrown by service.deleteById");
    } catch (Exception e) {
    assertTrue(e instanceof TodoNotFoundException);
    }

    verify(repositoryMock, times(1)).findOne(ID);
    verifyNoMoreInteractions(repositoryMock);
    }

    The test passes

    And if change the code to verify(repositoryMock, times(5)).findOne(ID);
    It fails as expected.

    Is there anything wrong with the @test(expected = ) and @Rule annotations to test exceptions?

    • Petri Dec 9, 2014 @ 18:16

      If you use the @Test(expected= ...) and an exception is thrown, the remaining lines aren't invoked because an exception was thrown from the test method. When I wrote that example application, I thought that it is a good idea to add those lines anyway because they document the behavior of the tested service method. Now I think that adding those lines is a bad idea because they are dead code.

      If you are using Java 8 AND you want to verify that the correct method was invoked, you should read a blog post titled: Clean JUnit Throwable-Tests with Java 8 Lambdas. It explains how you can catch the exception and write assertions for it. If you decide to use this approach, you can also verify that the correct method was invoked because the exception is not thrown from the test method (you catch it).

  • Reji Feb 7, 2015 @ 10:24

    Hi Petri,

    Please comment on below doubt. I'm confused with the behavior of MockMvc.

    For this I removed the "add.jsp" from "/WEB-INF/jsp/todo/" and run the the first test(showAddTodoForm_ShouldCreateFormObjectAndRenderAddTodoForm) in WebApplicationContextTodoControllerTest.java

    I expected the test to fail with status 404 instead of 200. But the test was GREEN. I run the webapplication and navigated to "http://localhost:8080/todo/add" here I'm getting the 404.

    Is that mean we can't rely on the MockMvc tests to verify our web application? Also please let me know how can I correct this problem

    Thanks.

    • Petri Feb 7, 2015 @ 10:52

      Spring MVC Test doesn't "render" JSP pages because it doesn't use a servlet container and you cannot render JSP pages without using one. In other words,

      • You can test that the correct JSP page should be rendered.
      • You cannot test that it is rendered correctly.

      If you use a template technology that doesn't need a servlet container (i.e. Thymeleaf, Freemarker, Velocity, and so on), you can also test that the page is rendered correctly.

      In other words, if you use JSP as a view technology, you cannot write tests which ensure that the user interface of your application is working correctly. If you want to write these tests, you have to deploy your application into a servlet container and use some other testing framework.

      • Reji Feb 8, 2015 @ 16:39

        Thanks Petri. Can you please suggest some testing frameworks that can be used to test page rendering?

        • Petri Feb 10, 2015 @ 21:57

          I haven't written UI tests for a few years, but when I did write them, I used Selenium. However, you might want to search for other options too because I am not a UI testing expert.

  • Jennifer Apr 9, 2015 @ 5:27

    Hi Petri,
    I have following two classes and want to write a Junit test for the generatePdfJasperReport() method by using mokito, but I do not know how. I do not have problem to write normal junit test for controller, just do not know how to write Junit test for jasper report controller. Do you know how?

    Thank you in advance

    public class Employee {
    private int employeeId;
    private String lastName;
    private String firstName;
    private String title;
    private Date hireDate;

    // Set and get methods
    ....
    }

    @Controller
    @RequestMapping(value = EmployeeJasperReportController.BASE_REQUEST_MAPPING)
    public class EmployeeJasperReportController extends BaseController {
    public static final String JASPER_DIR = "/WEB-INF/reports/employee";
    public static final String BASE_REQUEST_MAPPING = "/EmployeeReport";
    public static final String EMPLOYEE_REPORT = "EmployeeReport";

    public ModelAndView generatePdfJasperReport(HttpServletRequest request, HttpSession session, HttpServletResponse respose) throws Exception {
    Employee employeeObj = new Employee();
    List employeeList = new ArrayList();
    employeeList.add(employee);

    Map model = new HashMap();
    JRBeanCollectionDataSource reportDataSource = new JRBeanCollectionDataSource(employeeList, false);
    model.put("dataSource", reportDataSource);
    model.put(JRParameter.IS_IGNORE_PAGINATION, Boolean.FALSE);

    setRelativeReportPathForJasperReports(session, model);

    return new ModelAndView(EMPLOYEE_REPORT, model);
    }

    protected void setRelativeReportPathForJasperReports(HttpSession session, Map model) throws FileNotFoundException {
    // setting relative path for report
    String reportsDirPath = session.getServletContext().getRealPath(JASPER_DIR);
    File reportsDir = new File(reportsDirPath);
    if(! reportsDir.isDirectory()) {
    throw new FileNotFoundException(String.valueOf(reportsDir));
    }
    model.put(JRParameter.REPORT_FILE_RESOLVER, new SimpleFileResolver(reportsDir));
    }
    }

    • Petri Apr 9, 2015 @ 10:25

      Hi Jennifer,

      what kind of unit tests do you want to write for the generatePdfJasperReport() method? The reason why I am asking this is that this method seems to be doing three things:

      1. It creates an employee list (I assume that in real code this is fetched from service) and creates a JasperReports datasource that contains this list. Also, it sets the created datasource to the model.
      2. It enables pagination and sets this information to the model.
      3. It sets the report directory to the model.

      You can test three things quite easily by asserting the contents of the Model object. However, these tests doesn't ensure that the created report contains the correct data because the report creation logic is not found from this controller. I assume that this logic is found from a JSP page. Am I correct?

      If you have added the report creation logic to a JSP page, you cannot test it by using the Spring MVC Test framework because it doesn't run your code inside a servlet container.

      By the way, you mentioned that you don't know how to unit test this controller method. Did you mean that don't know how you can assert the contents of the Model object when it contains these objects provided by JasperReports?

  • Karthik Jun 23, 2015 @ 13:16

    Hi Petri,
    Thanks for such a detailed explanation on spring controller testing, and I'm following the same to test my controller class. But I'm stuck at
    (add_DescriptionAndTitleAreTooLong_ShouldRenderFormViewAndReturnValidationErrorsForTitleAndDescription) this point
    I have the same form in my application also the same method with @valid @modelattribute but, I have not used @sessionAttributes in controller class.
    When I'm testing against the validation of the form , irrespective of the .param() value it is redirected to 302 status but the expected it 200 , since it is returning the redirected view path.
    I don't know what I'm missing and where I'm going wrong.
    Thank You

    • Petri Jun 23, 2015 @ 18:48

      Hi Karthik,

      It is kind of hard to say what could be wrong without seeing the actual code, but I have a few questions for you:

      • Does your code return the same status code when you try to submit the form manually?
      • Is the code found from your controller method invoked when you run your tests (or does the validation fail)?

      Also, these resources might be useful:

      In other words, check that:

      • You don't have hidden redirect somewhere.
      • You don't have multiple controller methods with same request mappings.
      • The correct response status is set if you redirect the request manually (by creating a RedirectView object or returning a view name that has the redirect: prefix).

      If you have any additional questions, don't hesitate to ask them!

      • karthik Jun 24, 2015 @ 8:07

        Hi Petri
        Thank you for the response,
        1) When Im trying to submit my form manually it is working properly, i.e., It is redirected to the specifies view("redirected: view name") with the valid entry, and returns to the specified view name with an invalid entry(view within the if block)
        2) Yes code found from your controller method is invoked when I run my tests. But whatever I give as the parameter i.e., in .param() irrespective of tat it is redirected (So I'm not able to test my controller method for the invalid entry)
        And I don't have any hidden redirect or multiple controller of same request mapping

        Thanks in advance

        Here is my code snippet and the test case for tat

        //IdEntryForm has only the ID of type String

        
        @RequestMapping(value = "/Id/", method = RequestMethod.POST)
        public String getID(@Valid @ModelAttribute("idEntryForm") IdEntryForm idForm, 
        					BindingResult result, 
        					Model model) {
        
        	if (result.hasErrors()) {
        		return "IdEntryView";
        	}
        	/* after this I have some setters of other class*/
        	return "redirect:/IdSearch/"
        }
        
        

        //My test case -To test the method for invalid entry

        
        @Test
        public void getIdTest_WithFormViewAndReturnValidationErrorsForit() throws Exception{
        	String Id = "2587419";(valid entry is the max length of 5)
        	mockMvc.perform(post("/Id/")
        			.contentType(MediaType.APPLICATION_FORM_URLENCODED)
        			.param("ID",Id)
        	)
        		.andDo(print())
        		.andExpect(status().isOk())                
        		.andExpect(view().name("IdEntryView"))
        		.andExpect(forwardedUrl("/WEB-INF/view/IdEntryView.jsp"))
        		.andExpect(model().attributeHasFieldErrors("idEntryForm",Id))
        		.andExpect(model().attribute("idEntryForm", hasProperty("ID", is(Id))))
        }
        
        
        • Petri Jun 24, 2015 @ 18:10

          I have to admit that I don't know what is wrong. However, because you mentioned that the validation works when you submit the form manually, I assume that the problem is somehow related to the configuration of your unit tests. Do you configure your unit tests by using the standalone configuration or the web application context based configuration?

          • karthik Jun 25, 2015 @ 7:13

            I have used standalone configuration , testing single controller class at a time
            here is my setup

            
            @InjectMocks
            IdSearch idSearch=new IdSearch();
            	
            @Before
            public void setup()throws Exception {
            	MockitoAnnotations.initMocks(this);
            		
            	InternalResourceViewResolver viewRes = new InternalResourceViewResolver();
            	viewRes.setPrefix("/WEB-INF/pages/");
            	viewRes.setSuffix(".jsp");
            	    
            	this.mockMvc = MockMvcBuilders.standaloneSetup(idSearch)
            						.setViewResolvers(viewResolver)
            						.build();
            }
            
            
          • katrhik Jun 25, 2015 @ 10:00

            Thank You Petri for giving me an hint, I resolved my issue. I had missed adding validator() in test configuration.

            Even though I have tested my if(){} condition successfully, still my cobertura shows red line. May I know the reson

  • Sana Jul 6, 2015 @ 9:54

    Hi Petri,

    You have give the idea about all kind of unit testing, But I have a problem with FileUpload Controller testing ,can you please give me the example for FileUpload testing which test for all the conditions like if the file is empty, or if the directory is not present and few more conditions

    Thanks

    • Petri Jul 6, 2015 @ 10:58

      Hi Sana,

      Check out This StackOverflow answer. It provides a working test which ensures that the file upload function is working as expected. That should help you get started.

      Also, you should follow these "rules":

      • If you want to test a situation where an empty file is uploaded, you simply need to pass an empty MockMultipartFile to the file() method.
      • If you want to test a situation where the directory is not present, you need to configure the system under test to use a directory that is not found.

      I wish that I could give you a better answer, but it's impossible to write a test case without seeing the tested code. If you can show it to me, I will give you a better answer.

      If you have any further questions, don't hesitate to ask them.

      • Sana Jul 6, 2015 @ 14:38

        Thank You Petri, here is the part my code

        
        @RequestMapping(value = "/FileUpload", method = RequestMethod.POST)
        public String getId(@RequestParam("file") MultipartFile batchFile, Model model) {
        	if (file.isEmpty()) {
        		return "redirect:/MyId/";
        	}
        
        	BufferedOutputStream stream = null;
        	BufferedReader reader = null;
        
        	try {
        		byte[] bytes = file.getBytes();
        
        		if (!dir.exists())
        			dir.mkdirs();
        		}
        	}
        	catch (Exception e){
         		return "redirect:/MyId/";
        	}
        }
        

        also please give me an idea of how to test the code within the try catch blocks

        Thank You

        • Petri Jul 6, 2015 @ 18:02

          It seems that you have test these things by combining two different techniques:

          The first technique requires that you create "correct" MockMultipartFile objects and "pass" them to the tested controller method by using the technique described in this SO answer. This useful when you write tests that cover the following situations:

          • If you want to test that the user is redirected to the '/MyId/' view when the uploaded file is empty, you need to create the uploaded MockMultipartFile by using this constructor and pass an empty byte array as the second constructor argument.
          • If you want to test that the user is redirected to the 'MyId/' view when an exception is thrown, you could extend the MockMultipartFile class and override its isEmpty() and getBytes() methods. The isEmpty() method must always return false, and the getBytes() should throw the exception.

          The second technique requires that use the @TemporaryFolder JUnit rule. This way you can create folders to the temporary directory that are deleted after the test case is invoked. This helps you to test the situation where the upload directory is not found.

  • Govardhan Vedams Jul 6, 2015 @ 13:24

    Hi Petri Good morning ,

    I got project "spring-mvc-test-examples-master" from GitHub , but in this involved multiple projects. Which is used for testing expected view in spring controller .

  • Govardhan Vedams Jul 6, 2015 @ 14:28

    Hi Petri,

    I got "controllers-unittest" project in my eclipse . I got below error at the time of maven clean and after maven install

    [INFO] --- maven-compiler-plugin:2.5.1:compile (default-compile) @ controllers-unittest ---
    [INFO] Compiling 12 source files to C:\Documents and Settings\Govardhan\Desktop\spring-mvc-test-examples-master\controllers-unittest\target\classes
    [INFO] -------------------------------------------------------------
    [ERROR] COMPILATION ERROR :
    [INFO] -------------------------------------------------------------
    [ERROR] Failure executing javac, but could not parse the error:
    javac: invalid target release: 1.7
    Usage: javac
    use -help for a list of possible options

    Update: I removed the irrelevant part - Petri

    • Petri Jul 6, 2015 @ 16:59

      Hi,

      Which Java version are you using? The example application requires Java 7 (or newer). I have to confess that I cannot remember if you can compile and run it with Java 6 without making some changes to its source code, but if you want to try it, you have follow these steps:

      First, you have to find the following line from the pom.xml file:

      
      <jdk.version>1.7</jdk.version>
      
      

      Second, you have to change it to:

      
      <jdk.version>1.6</jdk.version>
      
      

      I hope that this helped you solve your problem. If not, feel free to answer to this comment!

      • Govardhan Vedams Jul 7, 2015 @ 16:01

        I change to

        1.6

        Thank you , its working.

        • Petri Jul 7, 2015 @ 16:12

          You are welcome!

  • Govardhan Vedams Jul 7, 2015 @ 16:00

    HI Petri, I start thinking about controller testing .

    ==> In controllers-unittest application , How ” toDoData.xml and toDoData-add-expected.xml “are communicated.
    ==> I wrote records with id’s 1,2 and 6 in toDoData.xml . when i add record in toDoData-add-expected.xml . In this time
    ———–.sessionAttr(TodoController.MODEL_ATTRIBUTE_TODO, new TodoDTO() —————–
    this sessionAttr is generated id 7, how this know present id is 6 which is in toDoData.xml.

    • Petri Jul 7, 2015 @ 16:15

      Hi,

      This is actually a quite common problem. You can solve it by resetting the auto increment column before the test is invoked. I have written a blog post that describes how you can do it.

      • Anonymous Jul 8, 2015 @ 9:27

        Hi Petri Good morning,
        Thank you . Now I am reading blog , I get doubt
        may i know use of below line and this line is not used in controllers-unittest application . when we can use this ?
        @DatabaseSetup("no-todo-entries.xml") .

        • Petri Jul 8, 2015 @ 10:15

          The @DatabaseSetup annotation simply identifies the DbUnit data set that is used to initialize the database before each test method is invoked.

          The reason I applied that annotation at the method level is that the integration tests of that example application use different data sets for different test methods.

          Because the integration tests which I wrote for this blog post use the same data set, I applied the @DatabaseSetup annotation at the class level.

          You can get more information about this from the website of the Spring Test DbUnit library.

          • Govardhan Vedams Jul 8, 2015 @ 12:44

            Thank you petri, i try one example now.
            I added below files in my project
            1)toDoData.xml
            2)PersonControllerTest (ITTodoControllerTest)
            3)updated pom.xml
            need to add any more for testing expected view?
            -------------------------------------------------------------------------------------------------------------
            i got got null pointer exception. i share the code below

            
            @Controller
            @RequestMapping("/person")
            public class PersonController extends SuperController{
            	@RequestMapping(value ="/{id}", method = RequestMethod.GET)
            	public String viewForm(@PathVariable(value="id")Long personId, 
            							Person person, 
            							IPAddress ipAddress, 
            							HttpSession session, 
            							Model model) throws Exception {
            
            	}
            }
            
            

            ------------------------------------------------------------------------------------------------------------------

            
            @TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
                DirtiesContextTestExecutionListener.class,
                TransactionalTestExecutionListener.class,
                DbUnitTestExecutionListener.class })
            @WebAppConfiguration
            @DatabaseSetup("toDoData.xml")
            @ContextConfiguration("file:servlet-context.xml")
            public class SetupControllerTest33 {
            
            	private MockMvc mockMvc;
            	  
            	@Resource
            	private WebApplicationContext webApplicationContext;
            	  
            	@Before
            	public void setUp() {
            		mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
            								.build();
            	}
            	  
            	@Test
            	@ExpectedDatabase("toDoData.xml")
            	public void showAddTodoForm_ShouldRenderAddTodoForm() throws Exception {
            		mockMvc.perform(get("alan/setup/2{id}",2L))
            			.andExpect(status().isOk())
            			.andExpect(view().name(SetupController.VIEW_TODO_ADD))
            			.andExpect(forwardedUrl("/WEB-INF/views/setup-form.jsp"))
            			.andExpect(model().attribute(SetupController.MODEL_ATTRIBUTE_TODO, 
            						hasProperty("id", is(2L))
            			));
            	}
            }
            
            

            ----------------------------------------below is servlet-context.xml----------------------------------

            org.springframework.web.servlet.view.tiles2.TilesView

            /WEB-INF/hibernate.cfg.xml

            ${hibernate.dialect}
            ${hibernate.show_sql}
            update
            <!-- import.sql -->

            -------------------------------------------below tododata.xml--------------------------------------------

          • Govardhan Vedams Jul 8, 2015 @ 12:50

            continuation from above comment
            now I got
            java.lang.IllegalArgumentException: WebApplicationContext is required

            ——————————————-below tododata.xml——————————————–

          • Govardhan Vedams Jul 8, 2015 @ 12:51

            ——————————————-below tododata.xml——————————————–

            
            <todos setupId="2" 
            			title="" 
            			mode="" 
            			sourcePath="" 
            			deleteSource="true" 
            			scheduleEnabled="true" 
            			dateCreated="" 
            			dateModified="" 
            			createdBy="" 
            			ipList="" 
            			scheduler=""/>
            
            
          • Govardhan Vedams Jul 8, 2015 @ 13:06

            continuation from above comment
            solve some above exceptions from google , but now i get

            java.lang.IllegalArgumentException: Unable to load dataset from "toDoData.xml" using class com.github.springtestdbunit.dataset.FlatXmlDataSetLoader

          • Petri Jul 8, 2015 @ 13:20

            You need to put the data set file to the same package as the class which uses it. You can do this by either:

            • Putting the data set file to the same directory as the class file.
            • Creating a similar directory structure under the src/integration-test/resources directory and putting the data set file to that directory. For example, if the package of the test class is foo.bar, you need to put the data set file to the src/integration-test/resources/foo/bar directory.
          • Govardhan Vedams Jul 8, 2015 @ 13:41

            this site is so great . ok thank you .

            now i get

            com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails (`project`.`scheduler`, CONSTRAINT `FK_id695164fqgy6meyjpunk1k` FOREIGN KEY (`SETUPID`) REFERENCES `setup` (`SETUPID`))

          • Anonymous Jul 8, 2015 @ 13:48

            continuation from above comment
            here just i am expecting edit form with record information which have id = 2 , but it shows cannot delete , below is test class method.

            
            @Test
            @ExpectedDatabase("toDoData.xml")
            public void showAddTodoForm_ShoulddRenderAddTodoForm() throws Exception {
            	mockMvc.perform(get("project/person/2{id}",2L))
            		.andExpect(status().isOk())
            		.andExpect(view().name(SetupController.VIEW_TODO_ADD))
            		.andExpect(forwardedUrl("/WEB-INF/views/setup-form.jsp"))
            		.andExpect(model().attribute(SetupController.MODEL_ATTRIBUTE_TODO, 
            						hasProperty("id", is(2L))
            		));
            }
            
            

            -----------------i got below exception-------------------------------------------------------------

            com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails (`project`.`scheduler`, CONSTRAINT `FK_id695164fqgy6meyjpunk1k` FOREIGN KEY (`SETUPID`) REFERENCES `setup` (`SETUPID`))

          • Petri Jul 8, 2015 @ 17:11

            The problem is that your data set has a setupId column that references to the setup table. However, the setup table doesn't have a row whose id is 2. In other words, you have to fix your data set so that all foreign key columns references to existing rows.

          • Govardhan Vedams Jul 9, 2015 @ 10:22

            Hi Petri Good morning.

            I am sorry . I got same exception , I shared below total my project.

            ----------------------------------------below is tododata.xml----------------------------------------------

            
            <setup SETUPID="2" 
            	TITLE="setup" 
            	MODE="online" 
            	SOURCEPATH="NULL" 
            	DELETESOURCE="0" 
            	SCHEDULEENABLED="1" 
            	DATECREATED="2015-06-24 15:49:34" 
            	DATEMODIFIED="NULL" 
            	CREATEDBY="1"/>
            
            <scheduler SETUPID="2" J
            	OBNAME="setup" 
            	ALERTGROUPID="1" 
            	STARTDATE="24-06-2015" 
            	ENDDATE="24-06-2015" 
            	SCHEDULETYPE="daily" 
            	HOURS="0" 
            	DAILYTIME="15:50"
            	WEEKLYTIME="" 
            	WEEKDAYS="" 
            	NEXTRUNTIME="NULL">
            
            

            Update: I removed the irrelevant files from this comment - Petri

          • Govardhan Vedams Jul 9, 2015 @ 10:37

            Continuation above comment

            Now I am listening your video in you tube

          • Petri Jul 9, 2015 @ 11:14

            Do you get the constraint violation error because the correct row is not found from the setup table, or do you get the constraint violation error because the CREATEDBY or ALERTGROUPID columns references to a row that is not found from the database? Remember that if your data set contains columns that references to other tables, you need insert the appropriate rows to all those tables before you can run your test case.

            It's impossible to know what is wrong because I am not familiar the structure of your database and you didn't add the stack trace to your comment. Can you add the stack trace here?

            By the way, this DbUnit FAQ entry might be useful to you: How to automatically order tables by their foreign keys?

          • Govardhan Vedams Jul 9, 2015 @ 12:21

            -----continuation above comment
            -----stack trace for above comment

            com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails (`alan`.`ipaddress`, CONSTRAINT `FK_p3uranrjk6r8ufu86xbwx9x5b` FOREIGN KEY (`SETUPID`) REFERENCES `setup` (`SETUPID`))

            Update: I removed the stack trace because the error message provides the required information - Petri

          • Petri Jul 9, 2015 @ 13:02

            Are you running your integration tests against an existing database that has data in it? The reason why I ask this is that the error message states that the SETUPID column of the ipaddress table has an id that is not found from the setup table.

            If so, you need to clean up your database before you insert the data found from your data set. If you haven't specified the value of the @DatabaseSetup annotation's type attribute, DbUnit will delete all rows from a database table when the table is found from the data set before it inserts new row(s) into that table.

            In other words, if you want to delete all rows from a database table (without inserting new rows), you have to add an empty "row" to your data set. For example, you can delete all rows from the ipaddress table by adding the following row to your data set file:

            
            <ipaddress/>
            
            
          • Govardhan Vedams Jul 9, 2015 @ 13:24

            Thank u ,I add ipaddress table data in data set. But here my setup and ipaddress table data is deleted in my db . why ?

            ----- and i got below exception
            org.dbunit.dataset.datatype.TypeCastException: Error casting value for table 'setup' and column 'DATEMODIFIED'

          • Govardhan Vedams Jul 9, 2015 @ 13:38

            Thank u ,I add ipaddress table data in data set. But my total records are deleted in below tables
            setup , schedular , ipaddress. now i am in confusion ? . i am doing testing for getting edit page for record which have id=2. see below method-----------
            -------------------------------------------------------------------------------
            @Test
            @ExpectedDatabase("toDoData.xml")
            public void showAddTodoForm_ShoulddRenderAddTodoForm() throws Exception {
            mockMvc.perform(get("project/person/2{id}",2L))
            .andExpect(status().isOk())
            .andExpect(view().name(SetupController.VIEW_TODO_ADD))
            .andExpect(forwardedUrl("/WEB-INF/views/setup-form.jsp"))
            .andExpect(model().attribute(SetupController.MODEL_ATTRIBUTE_TODO,
            hasProperty("id", is(2L))
            ));
            }
            ---------------------------------------------------------
            —– and i got below exception
            org.dbunit.dataset.datatype.TypeCastException: Error casting value for table ‘setup’ and column ‘DATEMODIFIED’

          • Petri Jul 9, 2015 @ 13:41

            Thank u ,I add ipaddress table data in data set. But here my setup and ipaddress table data is deleted in my db . why ?

            Did you add it before or after the the element that inserts one row to the setup table? If you added it after the setup element, the data is probably removed from the setup table because of the foreign key's ON DELETE action. If you don't know how foreign key constraints work in MySQL, read the section 13.1.17.3 Using FOREIGN KEY Constraints of the MySQL 5.6 Reference Manual.

            and i got below exception
            org.dbunit.dataset.datatype.TypeCastException: Error casting value for table ‘setup’ and column ‘DATEMODIFIED’

            You are probably using the wrong date format. Check the type of the column from the database and use the format that is described in this DbUnit FAQ entry: What are the date formats supported by DbUnit?

          • Govardhan Vedams Jul 9, 2015 @ 13:46

            Thank you petri .you are giving response quickly.. you are giving great support to me. Thank you so much .

          • Petri Jul 9, 2015 @ 14:01

            You are welcome.

  • Govardhan Vedams Jul 9, 2015 @ 15:49

    Thank you . I succeed in expected view , but I not get expected url .. I expected
    “/WEB-INF/views/setup.jsp” url, but i get “tiles/baselayout.jsp”
    below code in tiles.xml

    definition name="setup" extends="baseLayout"
    put-attribute name="body" value="/WEB-INF/views/setup.jsp" /
    /definition

  • Govardhan Vedams Jul 9, 2015 @ 16:22

    ----------------------------------------below code in test class----------------------------------

    
    mockSession = new MockHttpSession(wac.getServletContext(), UUID.randomUUID().toString());
    
    
    mockMvc.perform(get("/setup/{page}/{pageSize}",1,15)
    	.session(mockSession))
    	.andDo(MockMvcResultHandlers.print())
    	.andExpect(status().isOk())
    	.andExpect(view().name("setup"));
    
    

    ------------------------------below code in controller----------------------------------------

    
    getPageView(model,buildRequest(page,pageSize,""), super.getUserId(session));
    
    

    in above line at super.getUserId(session) get null value , when i hot coded with 1L then expected view is working . HOw can I pass session dynamically from test class.

    • Petri Jul 9, 2015 @ 16:42

      You have to set the userId to the session or the tested code doesn't find it (and returns null). You can set the userId to the session by using one of these two options:

      First, you can use the setAttribute(String name, Object value) method of the MockHttpSession class. If you decide to use this approach, your code should like this:

      
      mockSession = new MockHttpSession(wac.getServletContext(), UUID.randomUUID().toString());
      mockSession.setAttribute("userId", 1L);
      
      mockMvc.perform(get("/setup/{page}/{pageSize}",1,15)
      	.session(mockSession))
      	.andDo(MockMvcResultHandlers.print())
      	.andExpect(status().isOk())
      	.andExpect(view().name("setup"));
      
      

      Second, you can use the sessionAttr(String name, Object value) method of the MockHttpServletRequestBuilder class. If you decide to use this approach, your code should look like this:

      
      mockMvc.perform(get("/setup/{page}/{pageSize}",1,15)
      	.sessionAttr("userId", 1L)
      	.andDo(MockMvcResultHandlers.print())
      	.andExpect(status().isOk())
      	.andExpect(view().name("setup"));
      
      
      • Govardhan Vedams Jul 9, 2015 @ 17:22

        Thank you petri , i develop what u assigned to me for setting session attribute, but get exception .
        ----------------------below code in In super class-------------------------------------------

        
        @Controller
        @SessionAttributes({"userbean"})
        public class SuperController {
        	public String getUserName(HttpSession session){
        		UserBean userbean  = (UserBean)session.getAttribute("userbean");
        		return userbean.getUserName();
         	}
        	public Long getUserId(HttpSession session){
        		UserBean userbean  = (UserBean)session.getAttribute("userbean");
        		return userbean.getUserId();
         	}
        }
        
        

        ------------------below code in subclass--------------------------

        
        public class SupController extends SuperController {
        	@RequestMapping(value ="/{page}/{pageSize}", method = RequestMethod.GET)
        	public String getAll(@PathVariable("page") Integer page ,
        				@PathVariable("pageSize") Integer pageSize ,
        				Model model, 
        				HttpSession session) {
        		getPageView(model,buildRequest(page,pageSize,""), super.getUserId(session));
        		return "jspup";
            }
        }
        
        

        ----------------below code in test class--------------------------------------------------

        
        @Before	
        public void setUp() throws Exception {
        	MockitoAnnotations.initMocks(this);
        	MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new SetupController())
        			.build();
        
        	this.mockMvc = webAppContextSetup(wac).build();
        	request = new PageRequest();
        	response = new PageResponse();
        	mockSession = new MockHttpSession(wac.getServletContext(),
        		 	UUID.randomUUID().toString()
        	);
        	UserBean userBean =  (UserBean) mockSession.getAttribute("userBean");
        	// UserBean userBean=new UserBean();
        	userBean.setUserId(1L);
        	mockSession.setAttribute("userBean", userBean);
        }
        
        

        @Test
        ----------------
        --------------

        
        mockMvc.perform(get("/setup/{page}/{pageSize}",1,15)
        	.session(mockSession))
        	.andDo(MockMvcResultHandlers.print())
        	.andExpect(status().isOk())
        	.andExpect(view().name("setup"));
        
        

        error ==> getting null pointer exception at super.getUserId(session) .....

        • Petri Jul 9, 2015 @ 18:20

          To be honest, I have no idea why you get that NPE, but I noticed a few things that look a bit suspicious:

          • The request mapping of your controller method is "/{page}/{pageSize}", but you send the GET request to the url: "/setup/1/15". I assume that this is just a typo.
          • You create the MockMvc object twice (using standalone and web application based setup), but you use only the object that was created by using the web application context based setup. I assume that is not the problem why your test fails, but there is no need to do this.
          • You get the UserBean from the session after you have created the MockHttpSession object, but at this point you haven't set the user to the session. Thus, the getAttribute() method should return null and your test should throw NPE when you try to set the userId of the user. Is this a typo?

          Anyway, I wrote two small tests and everything worked as expected.

          My controller looks as follows:

          
          @Controller
          public class HomeController {
          
              @RequestMapping(value="/", method = RequestMethod.GET)
              public String showHomePage(HttpSession session) {
                  LOGGER.debug("UserId: {}", ((User)session.getAttribute("user")).getId());
                  return "index";
              }
          }
          
          

          I wrote two tests that put a User object to the session:

          
          @Test
          public void showHomePage1_ShouldRenderHomePage() throws Exception {
          	User user = new UserBuilder().id(10L).build();
          		
          	MockHttpSession session = new MockHttpSession();
          	session.setAttribute("user", user);
          		
          	mockMvc.perform(get("/")
          		.session(session)
          	)
          		.andExpect(status().isOk())
          		.andExpect(view().name("index"))
          		.andExpect(forwardedUrl("/WEB-INF/jsp/index.jsp"));
          }
          
          @Test
          public void showHomePage2_ShouldRenderHomePage() throws Exception {
          	User user = new UserBuilder().id(10L).build();
          	mockMvc.perform(get("/")
          		.sessionAttr("user", user)
          	)
          		.andExpect(status().isOk())
          		.andExpect(view().name("index"))
          		.andExpect(forwardedUrl("/WEB-INF/jsp/index.jsp"));
          }
          
          

          When I ran these tests, the following line was written to the log file:

          
          DEBUG - HomeController             - UserId: 10
          
          

          It seems that there is something wrong with your code, but I cannot figure out what it is. :( I recommend that you use a debugger and monitor the internal state of the MockHttpSession object which you create in the setUp() method.

          • Govardhan Vedams Jul 10, 2015 @ 10:07

            Hi Petri Good morning .

            Thank you .

          • Govardhan Vedams Jul 10, 2015 @ 10:17

            User user = new UserBuilder().id(10L).build();

            How can i get UserBuilder. I think UserBuilder is customized class. I dont have UserBuildr class in my project . I have only User class.

          • Petri Jul 10, 2015 @ 10:46

            The UserBuilder class is a custom class found from the example application of my Spring Social tutorial. It is not useful to you because the User class of that example application and your User class are totally different beasts.

  • Govardhan Vedams Jul 9, 2015 @ 16:43

    Thank you petri ,
    I am using below code for expected url

    
    .andExpect(content().source(containsString("Manage Entities"));
    
    

    eclipse shows content() and containsString() are undefined , I am searching maven dependencies ,
    but i can't get solution.

    • Petri Jul 9, 2015 @ 16:49

      Unfortunately I don't know what is wrong.

  • Govardhan Vedams Jul 13, 2015 @ 13:06

    Hi Petri .

    I add this ==> .andExpect(content().contentType("/WEB-INF/views/setup.jsp"));

    but I got below error

    org.springframework.http.InvalidMediaTypeException: Invalid mime type "/WEB-INF/views/setup.jsp": type must not be empty

  • Govardhan Vedams Jul 16, 2015 @ 11:00

    Hi Petri , Good morning . I removed one pojo1 from pojo2 (mean remove one column(pojo1) from another table(pojo2)) .
    I removed pojo1and pojo2 realations in both hbm,xml ,
    removed column properties in .sql file,
    removed column1(pojo1) of set and get methods in both pojo2 classes only,
    column of pojo1 is removed in pojo2 table using sql alter query. but i got below exception at the time of saving pojo2 info in db.

    org.springframework.dao.InvalidDataAccessResourceUsageException: could not execute statement; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: could not execute statement

    org.hibernate.exception.SQLGrammarException: could not execute statement

    Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'ERMA' (column1) in 'field list'

    • Govardhan Vedams Jul 16, 2015 @ 12:50

      Thank u petri ,
      I solve above bug, I am not execute the triggers related the tables in my db.

      • Petri Jul 16, 2015 @ 17:18

        Hi,

        It is good to hear that you were able to solve your problem!

        I am not execute the triggers related the tables in my db.

        Unfortunately I cannot know what is wrong without seeing the CREATE TRIGGER statements. Also, I need to see the database query or queries that should activate the trigger(s).

  • Kalpana Aug 18, 2015 @ 19:15

    Hello Petri ,

    I am using spring boot. I am trying to write the Junit test for the controller. Can I follow the same as you mentioned in the blog ?

    • Petri Aug 18, 2015 @ 22:25

      Hi Kalpana,

      I am using spring boot. I am trying to write the Junit test for the controller. Can I follow the same as you mentioned in the blog ?

      Yes. I recommend that you use the standalone configuration for your unit tests and the web application context based setup for your integration tests.

      The Spring Boot Reference Guide has a section that describes how you can write tests for Spring Boot applications. You can also take a look at this example application. It uses Spring Boot and has a few simple unit and integration tests.

      • Kalpana Sep 1, 2015 @ 21:46

        thank you for your reply.

        In my controller I am also checking BindingResult and setting variables for model, how to write unit test /Integration test for those functions in controller?
        for Example :
        In my controller:

        
        public String test1(@ModelAttribute("bean1") Bean1, 
        					BindingResult results,
        					Model model){
        	bean1.initialize();
        	if(results.hasErrors())
        		retunr "page1";
        
        	bean1.setDetails(myservice.getdetails(true);//Calling service to get list
        	model.addAttribute("bean1","bean1");
        	return "page1";
        }
        
        

        do you have any suggestions for this type of functions ?

        thanks ,
        Kalpana

        • Petri Sep 2, 2015 @ 10:00

          This blog post provides a few examples of unit tests that can be used to test controller methods like this (Test 1: Validation Fails and Test 2: Todo Entry Is Added to The Database).

          If you want to write integration tests for this controller method, you should take a look at my Spring MVC Test tutorial (search for 'Integration Testing of Spring MVC Applications').

      • Kalpana Sep 2, 2015 @ 0:03

        Also Petri, in your example you mentioned that 'NesterRunner.class' , i tried to import the same as you mentioned 'import com.nitorcreations.junit.runners.NestedRunner;' but still it couldnt recognize the nitorcreations.

        • Petri Sep 2, 2015 @ 9:53

          Did you add the the required dependencies into your pom.xml file? If you did not do it, follow the instructions given in this blog post.

          • Anonymous Sep 8, 2015 @ 19:47

            Hello Petri,

            Thank you for your reply . In my controller i am calling service to get the list from the database . While writing the unit test , it throws null pointer exception , do you have any clue how to overcome that one ?

            public String test1(@ModelAttribute("bean1") Bean1,
            BindingResult results,
            Model model){
            bean1.initialize();
            if(results.hasErrors())
            retunr "page1";

            bean1.setDetails(myservice.getdetails(true);//Calling service to get list
            model.addAttribute("bean1","bean1");
            return "page1";
            }

          • Petri Sep 9, 2015 @ 18:03

            Hi,

            If you get a NullPointerException when the tested controller calls the service, the service isn't injected property (it's null). Do you use web application context based setup or standalone setup?

  • Michael P Sep 3, 2015 @ 18:27

    I see this method and I have trouble with it at an enterprise level and perhaps other developers can weigh in. You should differentiate the concept of Unit testing from Integration/Functional/Black Box testing. When you require testing at the dispatcher servlet level, your unit is now very large. If you want to check how mappings are mapping to individual methods, that test seems better suited toward functional testing (as in with Selenium or another tool like that). When you test for whether or not a 200 status code is returned or if the todo/lists resolves to the correct jsp, you are testing Spring itself! These tests should **already be covered** by Spring's own unit tests! For your Unit test in the example above, for the TodoController with a single findAll() method, your unit tests for this controller **should only be** testing this findAll() method by a) mocking the service, b) passing in good/bad input to the method and c) validating behaviors and output for this method alone. A true unit test would not encompass testing the behaviors of Spring, because that is also to say that you do not trust the contracts that Spring has set up.

    Of course, I am not saying that what I am suggesting is the only way to test - this approach relieves functional testing to a degree by combining into unit testing. It just also gives an alternate (in my opinion, incorrect) definition of unit testing, because you have expanded your unit across multiple classes and methods.

    Please let me know your thoughts.

    • Petri Sep 5, 2015 @ 10:07

      Hi Michael,

      You should differentiate the concept of Unit testing from Integration/Functional/Black Box testing.

      Definitely.

      When you require testing at the dispatcher servlet level, your unit is now very large.

      I don't necessarily require it, but this tutorial describes how the reader can do it if he/she wants to.

      If you want to check how mappings are mapping to individual methods, that test seems better suited toward functional testing (as in with Selenium or another tool like that). When you test for whether or not a 200 status code is returned or if the todo/lists resolves to the correct jsp, you are testing Spring itself! These tests should **already be covered** by Spring’s own unit tests!

      Yes and no. I agree that Spring's own unit tests should definitely cover its features, but we have to write tests that cover the contract of the tested component. I think that the HTTP status code, the returned JSON, or the rendered JSP page are a part of that contract. That is why we must test them, but we must also find the answers to these questions:

      • Should we test these things by using unit, integration, or end-to-end tests?
      • What tools should we use?

      By the way, Jim Coplien has written a thought provoking article titled: Why Most Unit Testing is Waste. It tries to provide some answers to the first question.

      For your Unit test in the example above, for the TodoController with a single findAll() method, your unit tests for this controller **should only be** testing this findAll() method by a) mocking the service, b) passing in good/bad input to the method and c) validating behaviors and output for this method alone. A true unit test would not encompass testing the behaviors of Spring, because that is also to say that you do not trust the contracts that Spring has set up.

      This depends from the definition of the tested unit. If we think that the tested unit is one class, we should definitely ignore Spring Test MVC and write "normal" unit tests. On the other hand, if we think that the tested unit is something that provides a useful feature (like a public API method), we should use Spring Test MVC.

      Also, trusting Spring is one thing, but Spring doesn't specify how the tested controller should behave. It only provides the framework that we use when we build our application on the top of it.

      Of course, I am not saying that what I am suggesting is the only way to test – this approach relieves functional testing to a degree by combining into unit testing. It just also gives an alternate (in my opinion, incorrect) definition of unit testing, because you have expanded your unit across multiple classes and methods.

      I have actually written these so called normal unit tests and I noticed that they have quite many problems. The two most painful problems are:

      • If our controller method takes Spring specified object as a method parameter, we have to create these objects in our test class. Also, we have to initialize their internal state in our test class.
      • If we want to write tests, which ensure that the validation is working properly, we have to validate the form object in our test class.

      In other words, because I had to setup a lot of Spring related stuff in my test class, my unit tests were long, hard to read, and really error prone. This was actually one of the biggest reasons why people didn't write unit tests for Spring MVC controllers. They used integration tests or end-to-end tests.

      I chose a different path. I decided to start using Spring Test MVC because it helps me to write unit tests that explains the contract of the tested controller method to the person who reads them. Also, if I do TDD (I am still struggling with it), Spring Test MVC makes it a lot easier because I can concentrate on testing the contract of the tested controller method and ignore the burdensome setup phase.

  • Gaetano Oct 9, 2015 @ 14:09

    Hi Petri, I'm bothering you early than expected :)

    In my application, in order to provide feedbacks to the user I have small home made framework that is based on a class called Alert which has 3 properties:

    
    String msgCode;	
    String message;			
    Priority priority; <-- this is an ENUM
    
    

    Anyway, depending on the scenario, I create a ArrayList of Alerts and add it to the model
    These Alerts are displayed to the user in the appropriate way. Now my silly question
    I'm able to check that the ArrayList of Alerts is present in the model and that it is actually an ArrayList:

    
    .andExpect(model().attributeExists(Alert.ALERT_MSG))  		 
    .andExpect(model().attribute(Alert.ALERT_MSG, instanceOf(ArrayList.class))) 
    
    

    but how can I check the ArrayList's content? I have tried with hasPropertiy but it didn't get me anywhere. something like:

    
    .andExpect(model().attribute(Alert.ALERT_MSG, hasPropertiy ??))) 
    
    

    Do you have any suggestion?
    thanks
    Gaetano

    • Petri Oct 9, 2015 @ 21:52

      Hi!

      If you take a look at the StandAloneTodoControllerTest, you notice that there is a solution to this problem. You need to follow these steps:

      1. Verify that the list has the correct number of items.
      2. Ensure that the list has the correct items.

      The relevant part of my example looks as follows:

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

      However, when I took a look at the the Javadoc of the Matchers class, I noticed that it also has a hasItems(Matcher<? super T>... itemMatchers) method that makes this assertion a bit more cleaner and easier to read. If you convert the previous example to use that method, it looks as follows:

      
      .andExpect(model().attribute("todos", hasSize(2)))
      .andExpect(model().attribute("todos", hasItems(
      	allOf(
      		hasProperty("id", is(1L)),
      		hasProperty("description", is("Lorem ipsum")),
      		hasProperty("title", is("Foo"))
      	),
      	allOf(
      		hasProperty("id", is(2L)),
      		hasProperty("description", is("Lorem ipsum")),
      		hasProperty("title", is("Bar"))
      	)
      )))
      
      
      • Anonymous Oct 12, 2015 @ 13:57

        Thanks it worked.
        below my code:

        
        .andExpect(model().attribute(Alert.ALERT_MSG, instanceOf(ArrayList.class))) 
        .andExpect(model().attribute(Alert.ALERT_MSG, hasSize(1))) 					
        .andExpect(model().attribute(Alert.ALERT_MSG, hasItems(						
        	allOf(
        		hasProperty("msgCode",is("errMsg.usrCreation.emailPresent")),
        		hasProperty("priority",is(Alert.Priority.DANGER))
        )))) 
        
        
        • Petri Oct 12, 2015 @ 19:21

          You are welcome. I am happy to hear that you were able to solve your problem!

  • Gaetano Oct 9, 2015 @ 20:16

    Hello Petri
    apart from my previous question I'm also having problem testing Form Validation.
    The part of the application that I'm trying to test is basically User registration

    In my application I'm using a spring custom validator:

    
    import org.springframework.validation.ValidationUtils;
    import org.springframework.validation.Validator;
    
    @Component
    public class UserValidator implements Validator {
    	
    	@Override
    	public boolean supports(Class clazz) {
    		return User.class.equals(clazz);
    	}
    	
    	@Override
    	public void validate(Object target, Errors errors) {
    		//...
    		ValidationUtils.rejectIfEmptyOrWhitespace(errors, 
    				"firstName", 
    				"errMsg.usrCreation.nameMandatory"
    		);
    		ValidationUtils.rejectIfEmptyOrWhitespace(errors, 
    				"secondName", 
    				"errMsg.usrCreation.surnameMandatory"
    		);
    
    		// here some other validation logic
    	}	
    }
    
    

    In my Controller:

    
    import org.springframework.validation.BindingResult;
    import org.springframework.validation.Validator;
    
    @Controller
    public class UserController {
    
    	@Autowired
    	@Qualifier("userValidator")
    	private Validator validator;
    	...
    	@InitBinder("aUser") 
    	public void initBinder(WebDataBinder binder) {
    
    		binder.setValidator(validator);
    	}
    
    	@RequestMapping(value = "/register", method = RequestMethod.POST)
    	public String createUser(@Valid @ModelAttribute("aUser") User user,
    							  BindingResult result,
    							  Model model,
    							  Locale locale) {
    		
    		if(result.hasErrors()) {
    			System.out.println("Form has errors");
    			return "newUserProfile"; //<-- returns back the the form view 
    		}
    		// call the service to create the User
    		return "success";
    }
    
    

    User.java is a very simple bean
    with four properties , and related getters and setters

    
    public class UserParameters {
    	
    	private String firstName;
    	private String secondName;
    	private String email;
    	private String pwd;
    	
    	//constructors & mutators	
    }
    
    

    If I run the application, the validation works perfectly fine, for example if I don't provide a Name I get sent back to the User registration page and the error is displayed alongside the appropriate field

    During the JUnit Test the following assertion .andExpect(model().attributeHasErrors("aUser")) keeps failing.
    It must be something related to Test configuration but I'm not able to figure it out. I'm using standalone approach, below the details of my test:

    
    @RunWith(MockitoJUnitRunner.class)
    public class UserControllerTest {
    
    	private MockMvc mockMvc;
    	
    	@Mock
    	private WebUserService webUserServiceMock;
    	
    	protected MessageSource messageSource;
    	
    	protected Validator validator;
    	
    	protected MessageSource messageSource() {
    		ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
    		messageSource.setBasename("/i18n/messages");
    		messageSource.setUseCodeAsDefaultMessage(true);
    		return messageSource;
    	}
    	
    	protected LocalValidatorFactoryBean validator() {
            return new LocalValidatorFactoryBean();
        }
    	
    	@Before
    	public void setUp() {
    		//MockitoAnnotations.initMocks(this); <-- this was just an attempt but nothing changed
    		messageSource = messageSource();
    		UserProfileController  userProfileController = new UserProfileController();
    		ReflectionTestUtils.setField(userProfileController, "messageSource", messageSource);
    		ReflectionTestUtils.setField(userProfileController, "validator", validator);
    		ReflectionTestUtils.setField(userProfileController, 
    				"webUserService", 
    				webUserServiceMock
    		);
    		mockMvc = MockMvcBuilders.standaloneSetup(userProfileController)
    				.setValidator(validator())
    				.build();
    	}
    	
    	@Test 
    	public void testFormSubmitionFailureDueToMissingName() throws Exception {
    		WebUser webUser =  getWebUser(null,
    				"Doe", 
    				"johndoe@missing.com", 
    				"myV3ryCoMplexP@55w0rD"
    		); 
    					
    		mockMvc.perform(post("/register")
    						.param("firstName", webUser.getFirstName()) <-- here first name is null
    						.param("secondName",webUser.getLastName() )
    						.param("email", webUser.getEmailAddress())
    						.param("pwd", webUser.getPassword()))							
    					.andExpect(model().attributeHasErrors("aUserParam")); <-- this keeps failing
    }
    
    

    Any suggestion will be very appreciated
    Thanks and regards,
    Gaetano

    • Petri Oct 9, 2015 @ 22:05

      Hi,

      I noticed something that can cause this. If you take a look at the StandaloneTodoControllerTest, you notice that you need to pass the name of the model attribute and the field names to the attributeHasErrors() method.

      You should replace your current assertion with this one:

      
       .andExpect(model().attributeHasFieldErrors("aUser", "firstName"))
      
      
      • Gaetano Nov 7, 2015 @ 18:27

        Hello Petri,

        I'm sorry to bother you again, I have tried your suggestion but it doesn't make any difference. I'm still not able to test the Validator.

        I did run JUnit in debug mode and I stuck a breakpoint in my Custom Validator, as I suspected, it is not called at all.

        It may sound dumb to you but really do not understand how the following code is supposed to found and instantiate my custom validator class...definitely must be something missing.

        
        ...
          protected LocalValidatorFactoryBean validator() {
                  return new LocalValidatorFactoryBean();
              }
        ...
          mockMvc = MockMvcBuilders.standaloneSetup(userProfileController)
        				.setValidator(validator()).build();
        
        

        I would be extremely grateful for any help
        cheers
        Gaetano

        • Petri Nov 8, 2015 @ 10:04

          Hi Gaetano,

          I’m sorry to bother you again, I have tried your suggestion but it doesn’t make any difference. I’m still not able to test the Validator.

          Don't worry. You are not bothering me. This is actually a quite interesting problem.

          I did run JUnit in debug mode and I stuck a breakpoint in my Custom Validator, as I suspected, it is not called at all.

          This is a vital clue.

          The code you mentioned doesn't create your custom validator. It creates a validator that can validate objects by using the Bean Validation API. Based on your source code, the custom validator bean is injected to your controller by using field injection.

          I noticed that you try to inject the custom validator by using the ReflectionTestUtils class:

          
          @Before
          public void setUp() {
          	messageSource = messageSource();
          	UserProfileController  userProfileController = new UserProfileController();
          	ReflectionTestUtils.setField(userProfileController, 
          			"messageSource", 
          			messageSource
          	);
          
          	//Where do you create the custom validator object?
          	ReflectionTestUtils.setField(userProfileController, "validator", validator);
          	
          	ReflectionTestUtils.setField(userProfileController, 
          			"webUserService", 
          			webUserServiceMock
          	);
          	
          	mockMvc = MockMvcBuilders.standaloneSetup(userProfileController)
          			.setValidator(validator())
          			.build();
          }
          
          

          How do you create the custom validator object? It seems the validator field of your controller class is null (or I am missing something really obvious).

          Also, it would be useful to know if the initBinder() method is invoked at all. If it is invoked, is the validator field null?

          • Gaetano Nov 10, 2015 @ 20:57

            Hello Petri,
            I have resolved my problem.
            As you suggested the custom validator were never created (doh!!!)
            I don't need at all the code to the following

            protected LocalValidatorFactoryBean validator() {
            return new LocalValidatorFactoryBean();
            }

            All I had to do is create an instance of the custom validator in the setUp method and all went smoothly.
            Below my updated code in case it might useful to someone else.

            Thanks for your help,
            cheers Gaetano


            @RunWith(MockitoJUnitRunner.class)
            public class UserControllerTest {

            private MockMvc mockMvc;

            @Mock
            private WebUserService webUserServiceMock;

            protected MessageSource messageSource;

            protected UserValidator validator; // <-- this is my custom validator

            protected MessageSource messageSource() {
            ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
            messageSource.setBasename("/i18n/messages");
            messageSource.setUseCodeAsDefaultMessage(true);
            return messageSource;
            }

            /* protected LocalValidatorFactoryBean validator() { <-- I don't need this at all!!
            return new LocalValidatorFactoryBean();
            }*/

            @Before
            public void setUp() {

            messageSource = messageSource();

            validator = new UserValidator(); //<-- here creating an instance of my custom validator

            UserProfileController userProfileController = new UserProfileController();
            ReflectionTestUtils.setField(userProfileController, "messageSource", messageSource);
            ReflectionTestUtils.setField(userProfileController, "validator", validator);
            ReflectionTestUtils.setField(userProfileController, "webUserService", webUserServiceMock
            );
            mockMvc = MockMvcBuilders.standaloneSetup(userProfileController)
            .setValidator(validator).build(); //<-- here setting the validator
            }

            @Test
            public void testFormSubmitionFailureDueToMissingName() throws Exception {
            WebUser webUser = getWebUser(null,
            "Doe",
            "johndoe@missing.com",
            "myV3ryCoMplexP@55w0rD"
            );

            mockMvc.perform(post("/register")
            .param("firstName", webUser.getFirstName()) <-- here first name is null
            .param("secondName",webUser.getLastName() )
            .param("email", webUser.getEmailAddress())
            .param("pwd", webUser.getPassword()))
            .andExpect(model().attributeHasFieldErrors("aUserParam", "firstName")); <-- this is now successful
            }

  • Dinh Duc Mar 11, 2016 @ 21:08

    Kun minä huomaan sun nimeen, olen satavarma sinä olet suomalainen! Minulla on vielä kysyttävän yhällä koodauksella ymmärtämättä. Mikä oikea todoBuilder() metodi on? Jos minulla on objektti, jonka nimi on Duuni, niin pystynkö kirjoittaa niin kuin

     
    Duuni duuni1 = new DuuniBuilder()
    
    

    Ymmärsinkö oikein vai väärin? Anteeksi olen uusi Spring Framework tulija. Oikeasti tämä on todella hyvä tutoriaali!

    • Petri Mar 12, 2016 @ 18:45

      Moi,

      olen suomalainen :) Kiitos kun kommentoit suomeksi, mutta vastaan englanniksi, koska noiden termien suomentaminen on hieman vaikeaa.

      The TodoBuilder class is a so called test data builder, and you have to create this class yourself. In other words, before you can create Duuni objects by using the DuuniBuilder class, you have to create the DuuniBuilder class.

      If you want to get more information about this, you should read my blog post that explains how you can create test data by using factory methods and test data builders.

      • Duc Dinh Mar 13, 2016 @ 17:35

        Thanks for your answers!

        Exactly! we should write in english, so everyone can understand what we say :). But I also already found the answer by reading the other tutorials from your blog yesterday. I'm working with Spring Framework version 4.0.0, maybe it's some different with your version that you used. I tried to write a junit test for my controller but i get error like below :
        org.mockito.exceptions.misusing.MissingMethodInvocationException:

        when() requires an argument which has to be 'a method call on a mock'.
        For example:
        when(mock.getArticles()).thenReturn(articles);

        Also, this error might show up because:
        1. you stub either of: final/private/equals()/hashCode() methods.
        Those methods *cannot* be stubbed/verified.
        2. inside when() you don't call method on mock but on some other object.
        3. the parent of the mocked class is not public.
        It is a limitation of the mock engine.

        at vjb.de.vietjob.controller.DuuniControllerTest.showDuuniTest(DuuniControllerTest.java:17)

        Here's my test code:

        
        @RunWith(SpringJUnit4ClassRunner.class)
        @ContextConfiguration
        @RequestMapping(value = "/")
        public class DuuniControllerTest {
        	private MockMvc mockMvc;
        
        	@Inject
        	private DuuniDao duuniDaoMock;
        	
        
        	@Test
        	public void showDuuniTest() throws Exception {
        		Duuni duuni1 = new DuuniImpl();
        		Duuni duuni2 = new DuuniImpl();
        		Duuni duuni3 = new DuuniImpl();
        	
        		when(duuniDaoMock.showDuuni()).thenReturn(
        				Arrays.asList(duuni1, duuni2, duuni3));
        		
        		mockMvc.perform(get("/tyopaikka.lista"))
        				.andExpect(status().isOk());
        				
        		verify(duuniDaoMock, times(1)).showDuuni();
        		verifyNoMoreInteractions(duuniDaoMock);
        
        	}
        }
        
        

        Could you explain where i was wrong?

        Update: I cleaned this comment a bit - Petri

        • Petri Mar 14, 2016 @ 20:36

          Hi,

          You have to ensure that the DuuniDao object is a mock object. If you configure the MockMvc object by using the web application context based setup, you have to divide your @Configuration classes in a such way that you can replace the actual DuuniDao object with a mock object when you run your unit tests (check this blog post for more details).

          That being said, I don't use the web application context based setup in my unit tests anymore. It's a bit confusing and managing the mock objects becomes pain in the ass if you have to create more than a few "mock beans". Nowadays I configure the MockMvc object by using the standalone setup. If you want to know how I do this, you should take a look at this blog post.

  • Will Nov 3, 2016 @ 17:41

    Hi, thanks for the article. When trying to implement this I get this error..
    java.lang.ClassFormatError: Absent Code attribute in method that is not native or abstract in class file javax/servlet/SessionTrackingMode
    Researching this it seems that the dependency..

    javax
    javaee-web-api
    6.0
    provided

    is the culprit. The javax:javaee-api is only intended for compiling against, not for running against, including unit tests. If you need classes suitable for running against, you really need a full Java EE application server. I am a bit lost here as I thought the point of a junit is that you don't need the application server. Any help would be much appreciated. Thanks.

    • Petri Nov 4, 2016 @ 22:18

      Hi,

      What Spring and Java version are you using? The reason why I ask this is that the ClassFormatException is thrown when the VM cannot read a class because it's malformed (or not a class file). I assume that root cause of this problem is that you might be using dependencies that are too old for your Java version, but I cannot be sure of this until you answer to my question.

      That being said, Spring MVC Test does require a few Java EE dependencies for running unit tests that use Spring MVC Test. I assume that it uses some classes from these modules in its mock servlet API implementation (I haven't read its code though).

      If you want to write unit tests for normal Spring MVC controllers that render a view, you can take a look at this POM file. It's an example application of my Test With Spring course, and that is the configuration I use nowadays (this is a rather old post).

      • Will Nov 10, 2016 @ 13:30

        Hi thanks for the reply. My POM file looks like this...I will look at this one.

        Update: Removed the POM file since Wordpress stripped all tags and the file was unreadable - Petri

        • Petri Nov 16, 2016 @ 22:05

          You are welcome. I hope that you were able to fix your POM file.

  • Kostyantyn Jan 15, 2017 @ 20:04

    Petri, hi!
    First of all I'd like to thank you for work you've done. I really enjoy reading your tutorial. Breaf, "meaty" and understandable. It's exactly what I've been looking for :)
    I'm just starting mastering Spring MVC app and I've got a problem, so I need some help: test configuration is done (based on your example), tests run successfully (in Eclipse: Run as -> Maven test). But when I try to run project from Eclipse (Run as -> Run on server) I receive an error "No qualifying bean of type [here goes type on one of my services] is defined: expected single matching bean but found 2". As far as I understand it happens because we have test context configuration class (where mocks are created). Could you, please, advice me how to fix this problem :)

    • Kostyantyn Jan 15, 2017 @ 20:18

      Maven -> Update project... solved the problem.

      • Petri Jan 18, 2017 @ 23:59

        Hi,

        It's good to know that were able to solve your problem. Also, if you want to get up-to-date examples about writing unit and integration tests for Spring web applications, you should take a look at the examples of my Test With Spring course. Note that even though the course is not free, the examples are (and will be) free.

  • Shanu Apr 20, 2017 @ 10:46

    Hi,

    I have a doubt regarding the TodoBuilder you've used.. What exactly is it ?
    Thanks so much for this tutorial. Please help!

    • Shanu Apr 20, 2017 @ 11:18

      If possible, please share the code for the class called "TodoBuilder".. I tried writing my own and am having trouble.

  • Chhavi Garg Jun 16, 2017 @ 21:19

    Hi,

    I am having a problem. Here is my code snippet.
    int res;
    ModelAndView mv = new ModelAndView();
    res = regService.checkForUser(register.getUserID());
    if (res > 0) {
    mv.addObject("user", "UserID already exist!!");
    mv.setViewName("signUp");
    return mv;
    }
    During the testing of this code I wrote ==> .andExpect(model().attribute("user",hasValue("UserID already exist!!")));
    I got this error.
    java.lang.AssertionError: Model attribute 'user'
    Expected: map with value "UserID already exist!!"
    got: null

    how to solve this problem?

    • Petri Jun 17, 2017 @ 9:11

      Hi,

      The hasValue() matcher verifies that a Map has at least one value that is provided as a method parameter. However, because model attribute called: 'user' is not a map, this assertion fails.

      Because you are writing an assertion for a String object, you can create the required matcher by invoking the is() method of the Matchers class.

      If you have any additional questions, don't hesitate to ask them.

  • Igor Laryukhin Jun 27, 2017 @ 2:08

    Hi,
    Many big thanks for such a great, very useful and detailed post! It saved me many days many times!

    • Petri Jun 27, 2017 @ 21:15

      You are welcome! Also, thank you for your kind words. I really appreciate them.

  • Deepak Aug 16, 2017 @ 21:49

    This kind of test looks like Integration test more than unit tests.
    mockMvc.perform(get("/todo/{id}

    Should I really need to do this? What value does it add on top of normal writing unit testing for methods? Can't I write Unit test as if it is simple methods?

    • Petri Aug 17, 2017 @ 22:59

      Hi,

      You are right. The tests that use the Spring MVC Test framework are a bit different than the tests which simply invoke the controller method and write assertions for the returned object (or thrown exception). To make matters more interesting, this a rather old blog post that configures the MockMvc object by using the application context based configuration. I don't use it anymore when I write unit tests because the standalone configuration allows me to select the components that I included in the system under test.

      I write unit tests which use the Spring MVC Test framework mainly because I think that it helps me to write tests which specify the expected behavior of the system under test. For example, I am not interested in writing assertions for the returned object that can be either a view name or a DTO. I want to write tests which describe that this controller method should render this view or return a JSON document that contains this data. In other words, using the fluent API of the Spring MVC Test framework makes more sense to me than writing the boilerplate code that is required if I write traditional unit tests.

      P.S. If you think that these tests are not valuable, you shouldn't write them. The idea of this blog post is not to convince anyone to use the Spring MVC Test framework. It simply demonstrates how you can use it.

  • akon Jan 9, 2018 @ 20:49

    In the video TodoController code is mismatching with this blog

    • Petri Jan 9, 2018 @ 21:05

      I assume that you are talking about this old video. If this is the case, you are correct. This blog post shows only the relevant part of the controller class and the video shows the entire controller class.

      Thank you for reporting this. I won't make any changes to this blog post because my goal is to rewrite this at some point (this is quite old and a bit outdated).

  • Sasha Aug 3, 2018 @ 13:53

    Hello Petri,

    I got a question related to the method 'add' in the controller class 'TodoController'.
    In particular, the return value you give there is a redirect to the name of the view and not to the url of a controller request (as recommended by the documentation).

    I wonder is such practice wrong or both redirecting to a view and to a url is ok? Like, for instance, if we are to redirect to a method @Request mapping ("/get") String getSomething() {'body of the method' return "ourView"}, then to redirect to this method, we can use both return "redirect:/get" and "redirect:ourView", is it so?

    The documentation, however, speaks only about redirecting to URLs, therefore I am uneasy about redirecting to a view: "The special redirect: prefix in a view name allows you to perform a redirect...The rest of the view name is the redirect URL(my note: AND NOT A VIEW)".
    Source: https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html

    • Petri Aug 9, 2018 @ 20:22

      Hi,

      Thank you for reporting this problem. It is an error that (most likely) happened when I replaced the constants found from the actual controller class with String literals. It is now fixed.

      Again, thank you for reporting this problem!

  • SJ Jul 19, 2019 @ 18:03

    Hi,
    I have a theoretical question on mock testing in general. In all your mock methods (finalAll, final/id, etc.) you have hardcoded the data that those service layer methods return. In a way you are getting what you predefined, which is expected. So what is the point of mocking service layer?
    Besides....if you hadnt mocked, for eg. findAll(), will the actual service layer method be called?

    • Petri Jul 20, 2019 @ 21:38

      Hi,

      Some test doubles (such as stubs and mocks) allow you isolate the system under test from its dependencies. This is a good idea because:

      • You can write FAST tests which don't use external services such as a database.
      • It's easy to test different scenarios because typically it's a lot easier to stub a method than to create a data set which inserts the required test data into the database.
      • If a unit test fails, it's easy to figure out what's wrong because you have to read only one class or a few classes (this depends from the size of the tested unit).

      The downside of unit testing is that unit tests don't ensure that the tested feature is working as expected because they test only a small part of the feature. That's why you need to write integration tests as well.

      if you hadnt mocked, for eg. findAll(), will the actual service layer method be called

      The example application of this blog post uses a configuration which creates a mock bean that replaces the real service. If I don't stub the findAll() method, Mockito will return a so called safe response (which in this case is an empty list).

      I don't use this configuration anymore because nowadays (this blog post was written six years ago) I like to create the required test doubles in my test class. The benefit of this approach is that it allows me to emphasize the dependencies of the system under test.

Leave a Reply