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

Unit Testing of Spring MVC Controllers: REST API

Old treasure map

Spring MVC provides an easy way to create REST APIs. However, writing comprehensive and fast unit tests for these APIs has been troublesome. The release of the Spring MVC Test framework gave us the possibility to write unit tests which are readable, comprehensive and fast.

This blog post describes how we can write unit tests for a REST API by using the Spring MVC Test framework. During this blog post we will write unit tests for controller methods which provide CRUD functions for todo entries.

Let’s get started.

Getting The Required Dependencies with Maven

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

  • Hamcrest 1.3 (hamcrest-all). We use Hamcrest matchers when we are writing assertions for the responses.
  • Junit 4.11. We need to exclude the hamcrest-core dependency because we already added the hamcrest-all dependency.
  • Mockito 1.9.5 (mockito-core). We use Mockito as our mocking library.
  • Spring Test 3.2.3.RELEASE
  • JsonPath 0.8.1 (json-path and json-path-assert). We use JsonPath when we are writing assertions for JSON documents returned by our REST API.

The relevant dependency declarations looks as follows:

<dependency>
	<groupId>org.hamcrest</groupId>
	<artifactId>hamcrest-all</artifactId>
	<version>1.3</version>
	<scope>test</scope>
</dependency>
<dependency>
	<groupId>junit</groupId>
	<artifactId>junit</artifactId>
	<version>4.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>
<dependency>
	<groupId>com.jayway.jsonpath</groupId>
	<artifactId>json-path</artifactId>
	<version>0.8.1</version>
	<scope>test</scope>
</dependency>
<dependency>
	<groupId>com.jayway.jsonpath</groupId>
	<artifactId>json-path-assert</artifactId>
	<version>0.8.1</version>
	<scope>test</scope>
</dependency>

Let’s move on and talk a bit about the configuration of our unit tests.

Configuring Our Unit Tests

The unit tests which we will write during this blog post use the web application context based configuration. This means that we configure the Spring MVC infrastructure by using either an application context configuration class or a XML configuration file.

Because the first part of this tutorial described the principles which we should follow when we are configuring the application context of our application, this issue is not discussed in this blog post.

However, there is one thing that we have to address here.

The application context configuration class (or file) which configures the web layer of our example application does not create an exception resolver bean. The SimpleMappingExceptionResolver class used in the earlier parts of this tutorial maps exception class name to the view which is rendered when the configured exception is thrown.

This makes sense if we are implementing a “normal” Spring MVC application. However, if we are implementing a REST API, we want to transform exceptions into HTTP status codes. This behavior is provided by the ResponseStatusExceptionResolver class which is enabled by default.

Our example application also has a custom exception handler class which is annotated with the @ControllerAdvice annotation. This class handles validation errors and application specific exceptions. We will talk more about this class later in this blog post.

Let’s move on and find out how we can write unit tests for our REST API.

Writing Unit Tests for a REST API

Before we can start writing unit tests for our REST API, we need to understand two things:

Next we will see the Spring MVC Test framework in action and write unit tests for the following controller methods:

  • The first controller methods returns a list of todo entries.
  • The second controller method returns the information of a single todo entry.
  • The third controller method adds a new todo entry to the database and returns the added todo entry.

Get Todo Entries

The first controller method returns a list of todo entries which are found from the database. Let’s start by taking a look at the implementation of this method.

Expected Behavior

The controller method which returns all todo entries stored to the database is implemented by following these steps:

  1. It processes GET requests send to url ‘/api/todo’.
  2. It gets a list of Todo objects by calling the findAll() method of the TodoService interface. This method returns all todo entries which are stored to the database. These todo entries are always returned in the same order.
  3. It transforms the received list into a list of TodoDTO objects.
  4. It returns the list which contains TodoDTO objects.

The relevant part of the TodoController class looks as follows:

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

import java.util.ArrayList;
import java.util.List;

@Controller
public class TodoController {

    private TodoService service;

    @RequestMapping(value = "/api/todo", method = RequestMethod.GET)
    @ResponseBody
    public List<TodoDTO> findAll() {
        List<Todo> models = service.findAll();
        return createDTOs(models);
    }

    private List<TodoDTO> createDTOs(List<Todo> models) {
        List<TodoDTO> dtos = new ArrayList<>();

        for (Todo model: models) {
            dtos.add(createDTO(model));
        }

        return dtos;
    }

    private TodoDTO createDTO(Todo model) {
        TodoDTO dto = new TodoDTO();

        dto.setId(model.getId());
        dto.setDescription(model.getDescription());
        dto.setTitle(model.getTitle());

        return dto;
    }
}

When a list of TodoDTO objects is returned, Spring MVC transforms this list into a JSON document which contains a collection of objects. The returned JSON document looks as follows:

[
    {
        "id":1,
        "description":"Lorem ipsum",
        "title":"Foo"
    },
    {
        "id":2,
        "description":"Lorem ipsum",
        "title":"Bar"
    }
]

Let’s move on and write an unit test which ensures that this controller method is working as expected.

Test: Todo Entries Are Found

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

  1. Create the test data which is returned when the findAll() method of the TodoService interface is called. We create the test data by using a test data builder class.
  2. Configure our mock object to return the created test data when its findAll() method is invoked.
  3. Execute a GET request to url ‘/api/todo’.
  4. Verify that the HTTP status code 200 is returned.
  5. Verify that the content type of the response is ‘application/json’ and its character set is ‘UTF-8′.
  6. Get the collection of todo entries by using the JsonPath expression $ and ensure that that two todo entries are returned.
  7. Get the id, description, and title of the first todo entry by using JsonPath expressions $[0].id, $[0].description, and $[0].title. Verify that the correct values are returned.
  8. Get the id, description, and title of the second todo entry by using JsonPath expressions $[1].id, $[1].description, and $[1].title. Verify that the correct values are returned.
  9. Verify that the findAll() method of the TodoService interface is called only once.
  10. Ensure that no other methods of our mock object are called during the test.

The source code of our unit test looks as follows:

import org.junit.Test;
import org.junit.runner.RunWith;
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 java.util.Arrays;

import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
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 findAll_TodosFound_ShouldReturnFoundTodoEntries() 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("/api/todo"))
                .andExpect(status().isOk())
                .andExpect(content().contentType(TestUtil.APPLICATION_JSON_UTF8))
                .andExpect(jsonPath("$", hasSize(2)))
                .andExpect(jsonPath("$[0].id", is(1)))
                .andExpect(jsonPath("$[0].description", is("Lorem ipsum")))
                .andExpect(jsonPath("$[0].title", is("Foo")))
                .andExpect(jsonPath("$[1].id", is(2)))
                .andExpect(jsonPath("$[1].description", is("Lorem ipsum")))
                .andExpect(jsonPath("$[1].title", is("Bar")));

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

Our unit test uses a constant called APPLICATION_JSON_UTF8 which is declared in the TestUtil class. The value of that constant is a MediaType object which content type is ‘application/json’ and character set is ‘UTF-8′.

The relevant part of the TestUtil class looks as follows:

public class TestUtil {

    public static final MediaType APPLICATION_JSON_UTF8 = new MediaType(MediaType.APPLICATION_JSON.getType(),
																		MediaType.APPLICATION_JSON.getSubtype(), 						
																		Charset.forName("utf8")						
																		);
}

Get Todo Entry

The second controller method which we have to test returns the information of a single todo entry. Let’s find out how this controller method is implemented.

Expected Behavior

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

  1. It processes GET requests send to url ‘/api/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 transforms the Todo object into a TodoDTO object.
  4. It returns the created TodoDTO object.

The source code of our controller method looks as follows:

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

@Controller
public class TodoController {

    private TodoService service;

    @RequestMapping(value = "/api/todo/{id}", method = RequestMethod.GET)
    @ResponseBody
    public TodoDTO findById(@PathVariable("id") Long id) throws TodoNotFoundException {
        Todo found = service.findById(id);
        return createDTO(found);
    }

    private TodoDTO createDTO(Todo model) {
        TodoDTO dto = new TodoDTO();

        dto.setId(model.getId());
        dto.setDescription(model.getDescription());
        dto.setTitle(model.getTitle());

        return dto;
    }
}

The JSON document which is returned to the client looks as follows:

{
    "id":1,
    "description":"Lorem ipsum",
    "title":"Foo"
}

Our next question is:

What happens when a TodoNotFoundException is thrown?

Our example application has an exception handler class which handles application specific exceptions thrown by our controller classes. This class has an exception handler method which is called when a TodoNotFoundException is thrown. The implementation of this method writes a new log message to the log file and ensures that the HTTP status code 404 is send back to the client.

The relevant part of the RestErrorHandler class looks as follows:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;

@ControllerAdvice
public class RestErrorHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(RestErrorHandler.class);

    @ExceptionHandler(TodoNotFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public void handleTodoNotFoundException(TodoNotFoundException ex) {
        LOGGER.debug("handling 404 error on a todo entry");
    }
}

We have to write two unit tests for this controller method:

  1. We have to write a test which ensures that our application is working properly when the todo entry is not found.
  2. We have to write a test which verifies that the correct data is returned to the client 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 properly when a todo entry is not found. We can write an unit test which ensures this by following these steps:

  1. Configure our 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 ‘/api/todo/1′.
  3. Verify that the HTTP status code 404 is returned.
  4. Ensure that the findById() method of the TodoService interface is called only once by using the correct method parameter (1L).
  5. Verify that no other methods of the TodoService interface are called during this test.

The source code of our unit test looks as follows:

import org.junit.Test;
import org.junit.runner.RunWith;
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 static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
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_ShouldReturnHttpStatusCode404() throws Exception {
        when(todoServiceMock.findById(1L)).thenThrow(new TodoNotFoundException(""));

        mockMvc.perform(get("/api/todo/{id}", 1L))
                .andExpect(status().isNotFound());

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

Test 2: Todo Entry Is Found

Second, we must write a test which ensures that the correct data is returned when the requested todo entry is found. We can write a test which ensures this by following these steps:

  1. Create the Todo object which is returned when our service method is called. We create this 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 ‘/api/todo/1′.
  4. Verify that the HTTP status code 200 is returned.
  5. Verify that the content type of the response is ‘application/json’ and its character set is ‘UTF-8′.
  6. Get the id of the todo entry by using the JsonPath expression $.id and verify that the id is 1.
  7. Get the description of the todo entry by using the JsonPath expression $.description and verify that the description is “Lorem ipsum”.
  8. Get the title of the todo entry by using the JsonPath expression $.title and verify that the title is “Foo”.
  9. Ensure that the findById() method of the TodoService interface is called only once by using the correct method parameter (1L).
  10. Verify that the other methods of our mock object are not called during the test.

The source code of our unit test looks as follows:

import org.junit.Test;
import org.junit.runner.RunWith;
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 static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;

@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_ShouldReturnFoundTodoEntry() throws Exception {
        Todo found = new TodoBuilder()
                .id(1L)
                .description("Lorem ipsum")
                .title("Foo")
                .build();

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

        mockMvc.perform(get("/api/todo/{id}", 1L))
                .andExpect(status().isOk())
                .andExpect(content().contentType(TestUtil.APPLICATION_JSON_UTF8))
                .andExpect(jsonPath("$.id", is(1)))
                .andExpect(jsonPath("$.description", is("Lorem ipsum")))
                .andExpect(jsonPath("$.title", is("Foo")));

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

Add New Todo Entry

The third controller method adds a new todo entry to the database and returns the information of the added todo entry. Let’s move on and find out how it is implemented.

Expected Behavior

The controller method which adds new todo entries to the database is implemented by following these steps:

  1. It processes POST requests send to url ‘/api/todo’.
  2. It validates the TodoDTO object given as a method parameter. If the validation fails, a MethodArgumentNotValidException is thrown.
  3. It Adds a new todo entry to the database by calling the add() method of the TodoService interface and passes the TodoDTO object as a method parameter. This method adds a new todo entry to the database and returns the added todo entry.
  4. It transforms the created Todo object into a TodoDTO object.
  5. It returns the TodoDTO object.

The source code of our controller method looks as follows:

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

import javax.validation.Valid;

@Controller
public class TodoController {

    private TodoService service;

    @RequestMapping(value = "/api/todo", method = RequestMethod.POST)
    @ResponseBody
    public TodoDTO add(@Valid @RequestBody TodoDTO dto) {
        Todo added = service.add(dto);
        return createDTO(added);
    }

    private TodoDTO createDTO(Todo model) {
        TodoDTO dto = new TodoDTO();

        dto.setId(model.getId());
        dto.setDescription(model.getDescription());
        dto.setTitle(model.getTitle());

        return dto;
    }
}

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

As we can see, this class declares three validation constraints which are described in the following:

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

If the validation fails, our error handler component ensures that

  1. The HTTP status code 400 is returned to the client.
  2. The validation errors are returned to the client as a JSON document.

Because I have already written a blog post which describes how we can add validation to a REST API, the implementation of the error handler component is not discussed in this blog post.

However, we need to know what kind of a JSON document is returned to the client if the validation fails. This information is given in the following.

If the title and the description of the TodoDTO object are too long, the following JSON document is returned to the client:

{
    "fieldErrors":[
        {
            "path":"description",
            "message":"The maximum length of the description is 500 characters."
        },
        {
            "path":"title",
            "message":"The maximum length of the title is 100 characters."
        }
    ]
}

Note: Spring MVC does not guarantee the ordering of the field errors. In other words, the field errors are returned in random order. We have to take this into account when we are writing unit tests for this controller method.

On the other hand, if the validation does not fail, our controller method returns the following JSON document to the client:

{
    "id":1,
    "description":"description",
    "title":"todo"
}

We have to write two unit tests for this controller method:

  1. We have to write a test which ensures that our application is working properly when the validation fails.
  2. We have to write a test which ensures that our application is working properly when a new todo entry is added to the database.

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

Test 1: Validation Fails

Our first test ensures that our application is working properly when the validation of the added todo entry fails. We can write this test by following these steps:

  1. Create a title which has 101 characters.
  2. Create a description which has 501 characters.
  3. Create a new TodoDTO object by using our test data builder. Set the title and the description of the object.
  4. Execute a POST request to url ‘/api/todo’. Set the content type of the request to ‘application/json’. Set the character set of the request to ‘UTF-8′. Transform the created TodoDTO object into JSON bytes and send it in the body of the request.
  5. Verify that the HTTP status code 400 is returned.
  6. Verify that the content type of the response is ‘application/json’ and its content type is ‘UTF-8′.
  7. Fetch the field errors by using the JsonPath expression $.fieldErrors and ensure that two field errors are returned.
  8. Fetch all available paths by using the JsonPath expression $.fieldErrors[*].path and ensure that field errors about the title and description fields are found.
  9. Fetch all available error messages by using the JsonPath expression $.fieldErrors[*].message and ensure that error messages about the title and description fields are found.
  10. Verify that the methods of our mock object are not called during our test.

The source code of our unit test looks as follows:

import org.junit.Test;
import org.junit.runner.RunWith;
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 static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.hasSize;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;

@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_TitleAndDescriptionAreTooLong_ShouldReturnValidationErrorsForTitleAndDescription() throws Exception {
        String title = TestUtil.createStringWithLength(101);
        String description = TestUtil.createStringWithLength(501);

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

        mockMvc.perform(post("/api/todo")
                .contentType(TestUtil.APPLICATION_JSON_UTF8)
                .content(TestUtil.convertObjectToJsonBytes(dto))
        )
                .andExpect(status().isBadRequest())
                .andExpect(content().contentType(TestUtil.APPLICATION_JSON_UTF8))
                .andExpect(jsonPath("$.fieldErrors", hasSize(2)))
                .andExpect(jsonPath("$.fieldErrors[*].path", containsInAnyOrder("title", "description")))
                .andExpect(jsonPath("$.fieldErrors[*].message", containsInAnyOrder(
                        "The maximum length of the description is 500 characters.",
                        "The maximum length of the title is 100 characters."
                )));

        verifyZeroInteractions(todoServiceMock);
    }
}

Our unit test uses two static methods of the TestUtil class. These methods are described in the following:

  • The createStringWithLength(int length) method creates a new String object with the given length and returns the created object.
  • The convertObjectToJsonBytes(Object object) method converts the object given as a method parameter into a JSON document and returns the content of that document as a byte array.

The source code of the TestUtil class looks as follows:

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.MediaType;

import java.io.IOException;
import java.nio.charset.Charset;

public class TestUtil {

    public static final MediaType APPLICATION_JSON_UTF8 = new MediaType(MediaType.APPLICATION_JSON.getType(), MediaType.APPLICATION_JSON.getSubtype(), Charset.forName("utf8"));

    public static byte[] convertObjectToJsonBytes(Object object) throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        return mapper.writeValueAsBytes(object);
    }

    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

The second unit test 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 new TodoDTO object by using our test data builder. Set “legal” values to the title and description fields.
  2. Create a Todo object which is returned when the add() method of the TodoService interface is called.
  3. Configure our mock object to return the created Todo object when its add() method is called and a TodoDTO object is given as a parameter.
  4. Execute a POST request to url ‘/api/todo’. Set the content type of the request to ‘application/json’. Set the character set of the request to ‘UTF-8′. Transform the created TodoDTO object into JSON bytes and send it in the body of the request.
  5. Verify that the HTTP status code 200 is returned.
  6. Verify that the content type of the response is ‘application/json’ and its content type is ‘UTF-8′.
  7. Get the id of the returned todo entry by using the JsonPath expression $.id and verify that the id is 1.
  8. Get the description of the returned todo entry by using the JsonPath expression $.description and verify that the description is “description”.
  9. Get the title of the returned todo entry by using the JsonPath expression $.title and ensure that the title is “title”.
  10. Create an ArgumentCaptor object which can capture TodoDTO objects.
  11. Verify that the add() method of the TodoService interface is called only once and capture the object given as a parameter.
  12. Verify that the other methods of our mock object are not called during our test.
  13. Verify that the id of the captured TodoDTO object is null.
  14. Verify that the description of the captured TodoDTO object is “description”.
  15. Verify that the title of the captured TodoDTO object is “title”.

The source code of our unit test looks as follows:

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
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 static junit.framework.Assert.assertNull;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;

@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_ShouldAddTodoEntryAndReturnAddedEntry() throws Exception {
        TodoDTO dto = new TodoDTOBuilder()
                .description("description")
                .title("title")
                .build();

        Todo added = new TodoBuilder()
                .id(1L)
                .description("description")
                .title("title")
                .build();

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

        mockMvc.perform(post("/api/todo")
                .contentType(TestUtil.APPLICATION_JSON_UTF8)
                .content(TestUtil.convertObjectToJsonBytes(dto))
        )
                .andExpect(status().isOk())
                .andExpect(content().contentType(TestUtil.APPLICATION_JSON_UTF8))
                .andExpect(jsonPath("$.id", is(1)))
                .andExpect(jsonPath("$.description", is("description")))
                .andExpect(jsonPath("$.title", is("title")));

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

        TodoDTO dtoArgument = dtoCaptor.getValue();
        assertNull(dtoArgument.getId());
        assertThat(dtoArgument.getDescription(), is("description"));
        assertThat(dtoArgument.getTitle(), is("title"));
    }
}

Summary

We have now written unit tests for a REST API by using the Spring MVC Test framework. This tutorial has taught us four things:

  • We learned to write unit tests for controller methods which read information from the database.
  • We learned to write unit tests for controller methods which add information to the database.
  • We learned how we can transform DTO objects into JSON bytes and send the result of the transformation in the body of the request.
  • We learned how we can write assertions for JSON documents by using JsonPath expressions.

As always, the example application of this blog post is available at Github. I recommend that you check it out because it has a lot of unit tests which were not covered in this blog post.

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

About the Author

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

About Petri Kainulainen →

29 comments… add one

  • Hi Petri,
    Thank you for this very useful

    following step by step your recommendations

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations = { "classpath:rtls-management-test-context.xml", "classpath:rtls-management-application.xml" })
    @WebAppConfiguration
    @TransactionConfiguration(defaultRollback = true, transactionManager = "hibernatetransactionManager")
    @Transactional
    public class UserRestServiceTest {

    Logger logger = Logger.getLogger(UserRestServiceTest.class.getName());

    private MockMvc mockMvc;

    @Autowired
    private UserService userService;

    @Autowired
    private WebApplicationContext webApplicationContext;

    @Before
    public void setUp() {
    // We have to reset our mock between tests because the mock objects
    // are managed by the Spring container. If we would not reset them,
    // stubbing and verified behavior would "leak" from one test to another.
    Mockito.reset(userService);

    mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
    .build();
    }

    private User addUser() {
    logger.info("-> addUser");
    User user = new User();
    long id = 1;
    user.setId(id);
    user.setEnabled(true);
    user.setFirstname("youness");
    user.setUsername("admin");
    user.setName("lemrabet");
    user.setPassword("21232f297a57a5a743894a0e4a801fc3");
    user.setEmail("youness.lemrabet@gmail.com");
    logger.info(" findAllUsers");
    User user = addUser();
    // stubbing
    when(userService.findAll()).thenReturn(Arrays.asList(user));

    mockMvc.perform(get("/get/all"))
    .andExpect(status().isOk())
    .andExpect(
    content().contentType(TestUtil.APPLICATION_JSON_UTF8))
    .andExpect(jsonPath("$", hasSize(2)))
    .andExpect(jsonPath("$[0].id", is(1)))
    .andExpect(jsonPath("$[0].enabled", is(true)))
    .andExpect(jsonPath("$[0].firstname", is("youness")))
    .andExpect(jsonPath("$[0].username", is("admin")))
    .andExpect(jsonPath("$[0].name", is("lemrabet")))
    .andExpect(
    jsonPath("$[0].password",
    is("21232f297a57a5a743894a0e4a801fc3")))
    .andExpect(
    jsonPath("$[0].email", is("youness.lemrabet@gmail.com")));

    verify(userService, times(1)).findAll();
    verifyNoMoreInteractions(userService);

    logger.info("<- findAllUsers");
    }

    }

    I get the following error :
    Failed tests: findAllUsers(com.smartobjectsecurity.management.rest.user.UserRestServiceTest): Status expected: <200> but was: <404>

    Thank you for your help

    Reply
    • The status code 404 means that the tested controller method was not found. There are typically two reasons for this:

      1. The request is send to wrong url (typo in the request mapping).
      2. The controller class is not found during component scan. Check out that your component scan configuration is correct.

      I hope that this answered to your question.

      Reply
      • Hi Petri,

        Thanks for the wonderful post. I have implemented a similar kind of test in my environment but I am getting the 404 error. I have checked for the URL as well as included the @ComponentScan(basePackages = {“somepackage”}) .

        Do you recommend any other thing to be taken care of?

        Thanks in anticipation.

        Regards,
        Amishi Shah

        Reply
        • Hi,

          Usually when you get a 404 response status, the reason is that the URL is not correct or the controller class is not found during component scan. You mentioned that you checked the URL and configured the Spring container to scan the correct package.

          Did you remember to annotate the controller class with the @Controller or @RestController annotation?

          Reply
  • Hi
    I was wondering how I could write a test for testing xml response instead of json . Any sample code would be appreciated.

    I learnt from a lot from this tutorial thanks.

    Reply
  • hi Petri
    I have the code from the blog. this is probably something silly but would like your input if you can.
    Everything from the blog is almost the same except the integration test @ContextConfiguration
    which I changed to
    @ContextConfiguration(locations = {“classpath:spring/root-Context.xml”, “classpath:spring/app/servlet-context.xml”})
    as I made some changes to the files.

    The integration test run and executes through the controller fine but fails with a response content type

    Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 4.619 sec <<< FAILURE!
    testController(my.tests.web.SomeControllerIntegrationTest) Time elapsed: 4.331 sec <<< FAILURE!
    java.lang.AssertionError: Content type expected: but was:
    at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:60)
    at org.springframework.test.util.AssertionErrors.assertEquals(AssertionErrors.java:89)
    at org.springframework.test.web.servlet.result.ContentResultMatchers$1.match(ContentResultMatchers.java:71)
    at org.springframework.test.web.servlet.MockMvc$1.andExpect(MockMvc.java:141)
    The mockMvc object is expecting Json in the response but it is not so. Thanks in advance

    Reply
    • Hi Chris,

      There are no silly questions! I assume that you have tested the controller method manually and verified that it returns a JSON document in the request body.

      Are you using Spring 4? I remember that I was having a similar problem in a project which uses Spring 4 instead of Spring 3.2. I was able to solve it by setting the Accept request header:

      
      mockMvc.perform(get("/api/srv/maincontractor")
      		.accept(MediaType.APPLICATION_JSON)
      )
      //Add assertions here
      
      

      Is it possible to see the request mapping of the tested controller method (I don’t need to see the actual implementation)?

      Reply
      • Hi Petri,
        Yes. This is the controller method under test. It returns a list of terms in json format. If i do a mvn tomcat:run and test it using rest client I can see that it does.

        @RequestMapping(value = “/terms/”, method = RequestMethod.GET)
        public ModelAndView listTerms() {…}

        The version of spring are newer and so is the JSon mapper. I wonder why there is such a version to version compatibility issue with these both but this another animal.
        3.2.0.RELEASE
        1.9.2

        Reply
      • Hi Petri

        Thank you for your input previously. I think I see how I managed to screw this up. In my spring configuration. I am using ..

        which I changed now to

        .. turns out surprisingly that it actually does the conversion from pojo to json but renders it in plain text – which makes sense; so that it can be captured in a jsp etc., except it was not obvious.
        Many thanks again.

        Reply
        • Hi Chris,

          If you want to simplify your code a bit (I assume that you create the ModelAndView object inside the controller method), you can change the controller method to look like this:

          
          @RequestMapping(value = "/terms/", method = RequestMethod.GET)
          @ResponseBody
          public List<Terms> listTerms() {...}
          
          

          This simplifies the code of your controller method and more importantly, it fixes your content type problem.

          Reply
  • Hi Petri,

    First, I want to thank you for this great tutorial, it helps me a lot.

    I found an weird issue in bean validation, spring mvc controllers’ unit tests.
    To reproduce you juste have to upgrade your dependency for the jsp api to the lastest version and execute your first test in TodoControllerTest (`add_EmptyTodoEntry_ShouldReturnValidationErrorForTitle()`)
    Upgrading that dependency will make this test fail, because I don’t know how but it kinda bypass bean validation. So you should get a 201 instead of 400.

    Can you confirm this? Is it a known issue?
    Thanks in advance for your time.

    Reply
    • Hi Ahmad,

      actually I couldn’t reproduce this issue. Did you upgrade any other dependencies to newer versions?

      Reply
      • Hi Petri,

        Thanks for your reply, yes actually I upgraded other dependencies (sorry I forgot to mentionned):
        – Spring: 4.0.2.RELEASE
        – Hibernate Validator: 5.1.0.Final
        – Servlet Api: 3.1

        If I remember well, I fixed the issue, by adding a dependency to javaee-web-api: 7.0.

        Reply
      • Petri,

        If you still can’t reproduce it, I can send you a pull request with the pom.xml if you want.

        Thanks for your time.

        Reply
        • Hi Ahmad,

          actually I happened to run into this issue when I updated Hibernate Validator to version 5.1.0.Final. I was able to solve this issue by using Hibernate Validator 5.0.3.Final. I suspect that Spring Framework 4 doesn’t support Hibernate Validator 5.1.0.Final but I haven’t been able to confirm this.

          Reply
          • thank you man for this, I was getting very frustrated!!

          • You are welcome! I am happy to hear that I was able to help you out.

  • Petri, these examples are really nice! I particularly like how clean and fast the unit test runs of your controller’s.

    Do you do the same type of setup in Spring 4?

    If you have a simple setup and use SpringBoot org.springframework.boot.SpringApplication.Application to bootstrap the application, is there anything significant that would change with your solution?

    Also, I thought I saw that spring-mvc-test was added into the Spring framework in Spring 3.2 which of course was released later after your blog post about this. I assume you would be using that instead of spring-mvc-test separately or did they make any changes that creates overhead that slows down the unit tests significantly?

    Thanks,
    Magnus

    Reply
    • Actually the example application of this blog post uses Spring Framework 3.2.X and Spring MVC Test framework. The standalone project is called spring-test-mvc, and it is used in my blog posts which talk about the integration testing of Spring MVC Applications. There really isn’t any reason to use it anymore (unless you have to use Spring 3.1).

      I use the same setup for testing web applications which use Spring Framework 4, but I haven’t used Spring Boot yet so I cannot answer to your question. I am planning to write a Spring Boot tutorial in the future, and I will definitely address this issue in that tutorial.

      Reply
  • Hi Petri

    Thanks for the tutorial.

    I am having some issue to get it start. I have post the question to the Stackoverflow.

    http://stackoverflow.com/questions/24393684/constructor-threw-exception-nested-exception-is-java-lang-noclassdeffounderror

    let me know if you need more info, thanks.

    Reply
  • Hi Petri,

    I have a question about the your object creation. In the controller test class you build your test data using a builder, but when you’re converting your data with createDTO you don’t use a builder here. Is there a reason for this?

    Thanks,
    Paul

    Reply
  • Thank you for asking such a good question.

    I cannot remember what my reason was when I made the decision to use the builder pattern only in my tests (I probably didn’t have any reason for this), but nowadays I follow these “rules”:

    • If the DTO is read-only, I will use a builder pattern and mark all its field as final. Also, I won’t add setters to this DTO. This is handy if I want to just transform read-only information without exposing the internal data model of my application to the outside world.
    • If the information of the DTO can be modified (e.g. a form object or an object that is read from the request body), I will not use a builder pattern in my application. The reason for this is that Spring provides the object to my controller method when it is processing a request. When I return information back to the client of my API, I use a library to map entities into data transfer objects so that I can eliminate unnecessary “boiletplate” code (check out Dozer, jTransfo, and ModelMapper). However, I do use builder pattern in my tests because this way I can write tests that speak the language that is understood by domain experts.
    Reply
  • Hi Petri,

    Nice tutorial.
    When I tried to use : https://github.com/pkainulainen/spring-mvc-test-examples/tree/master/controllers-unittest I found that TodoBuilder class was missing.

    Can you guide me what went wrong? (Version I have used : https://github.com/pkainulainen/spring-mvc-test-examples/commit/93ea56b879a0af64f862c935de188cf4860a9dd0)

    Thanks,
    Akshay

    Reply
    • Hi Akshay,

      Unfortunately I am not sure what is going on because I can find the TodoBuilder class. :(

      Reply
  • without TodoBuilder this are piece of shit…

    Reply

Leave a Comment