About Petri Kainulainen

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

Unit Testing of Spring MVC Controllers: REST API

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.
 

Reference: Unit Testing of Spring MVC Controllers: REST API from our JCG partner Petri Kainulainen at the Petri Kainulainen blog.

Do you want to know how to develop your skillset to become a Java Rockstar?

Subscribe to our newsletter to start Rocking right now!

To get you started we give you two of our best selling eBooks for FREE!

JPA Mini Book

Learn how to leverage the power of JPA in order to create robust and flexible Java applications. With this Mini Book, you will get introduced to JPA and smoothly transition to more advanced concepts.

JVM Troubleshooting Guide

The Java virtual machine is really the foundation of any Java EE platform. Learn how to master it with this advanced guide!

Given email address is already subscribed, thank you!
Oops. Something went wrong. Please try again later.
Please provide a valid email address.
Thank you, your sign-up request was successful! Please check your e-mail inbox.
Please complete the CAPTCHA.
Please fill in the required fields.

5 Responses to "Unit Testing of Spring MVC Controllers: REST API"

  1. Andre says:

    Very good Tutorial!

  2. Anand Kumar says:

    Hi Petri. I saw your youtube videos as well and I am trying to implement this in my application, however I am getting some errors.

    My controller has a dependency on SampleService class, so my text context declared this

    However when I run the test, I am getting the below error.

    Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [com.anand.validationservice.service.SampleService] is defined: expected single matching bean but found 2: sampleService,sampleServiceImpl

    As you know sampleService is only an interface and I have sampleServiceImpl as the implementation of the same so why it is saying duplicate?

    In order to resolve this – I completely took the text context off and instead used the following way to create the instance of sampleService in the setUP method
    sampleServiceMock = Mockito.mock(SampleService.class);

    This is how my test class looks now:
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations = {“file:src/main/webapp/WEB-INF/spring-config/validationsvc-servlet.xml”})
    @WebAppConfiguration
    public class ValidationServiceRESTControllerTest {

    private MockMvc mockMvc;

    @Autowired
    SampleService sampleServiceMock;

    @Autowired
    private WebApplicationContext webApplicationContext;

    private SampleModel sampleModel;

    /**
    *
    * @throws Exception
    */
    @Before
    public void setUp() throws Exception {
    //Mock the service object
    sampleServiceMock = Mockito.mock(SampleService.class);
    //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(sampleServiceMock);
    //Get the MockMvc set up
    mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
    //Build the sampleModel
    sampleModel = new SampleModel();
    sampleModel.setAccountNumber(“12348900003371″);
    sampleModel.setCheckAmount(500.35);
    sampleModel.setFirstName(“Anand”);
    sampleModel.setMiddleName(“Kumar”);
    sampleModel.setLastName(“lName”);
    //sampleModel.setId(21);
    sampleModel.setRoutingNumber(“2414234513412″);
    sampleModel.setValidCheck(true);
    }

    /**
    *
    * @throws Exception
    */
    @Test
    public void testGetSample() throws Exception {
    Mockito.when(sampleServiceMock.getSample()).thenReturn(sampleModel);
    mockMvc.perform(get(“/api/v1/json/account/{accountNumber}”, 123456789)
    .accept(MediaType.parseMediaType(“application/json;charset=UTF-8″)))
    .andExpect(status().isOk())
    .andExpect(content().contentType(“application/json;charset=UTF-8″))
    .andExpect(jsonPath(“$.firstName”).value(“Anand”));
    }

    /**
    *
    * @throws Exception
    */
    @Test
    public void testGetSampleXML() throws Exception {
    Mockito.when(sampleServiceMock.getSample()).thenReturn(sampleModel);
    mockMvc.perform(get(“/api/v1/xml/account/{accountNumber}”, 123456789)
    .accept(MediaType.parseMediaType(“application/xml;charset=UTF-8″)))
    .andExpect(status().isOk())
    .andExpect(content().contentType(“application/xml;charset=UTF-8″))
    .andExpect(jsonPath(“$.firstName”).value(“Anand”));
    }

    }

    But it looks like the content type is not being set as I get the following error.

    java.lang.AssertionError: Content type not set
    at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:39)
    at org.springframework.test.util.AssertionErrors.assertTrue(AssertionErrors.java:72)
    at org.springframework.test.web.servlet.result.ContentResultMatchers$1.match(ContentResultMatchers.java:75)
    at org.springframework.test.web.servlet.MockMvc$1.andExpect(MockMvc.java:141)
    at com.westernalliancebancorp.validationservice.web.controller.ValidationServiceRESTControllerTest.testGetSample(ValidationServiceRESTControllerTest.java:82)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:74)
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:83)
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:72)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:231)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:88)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:71)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:174)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

    Does it ring any bell? Can you please help?

  3. About your first problem: Does your application context configuration file configure a component scan for service packages? The error message indicates that this is the case (Spring found two beans: your mock and the actual service implementation). You can fix this by splitting the configuration as explained in the first part of the tutorial:

    http://www.javacodegeeks.com/2013/07/unit-testing-of-spring-mvc-controllers-configuration-2.html

    About your second problem: Unfortunately it is impossible to say why the test fails without seeing the code of the controller method and your app context configuration. If you can add them here, I can investigate the issue further.

    A few questions though:

    1) It seems that you are creating the mock correctly but how do you set it to the controller?
    2) Are both of your tests failing?

  4. Barajati says:

    Please provide concise working solutions if you are serious about transferring your skills.

Leave a Reply


seven − 6 =



Java Code Geeks and all content copyright © 2010-2014, Exelixis Media Ltd | Terms of Use | Privacy Policy | Contact
All trademarks and registered trademarks appearing on Java Code Geeks are the property of their respective owners.
Java is a trademark or registered trademark of Oracle Corporation in the United States and other countries.
Java Code Geeks is not connected to Oracle Corporation and is not sponsored by Oracle Corporation.
Do you want to know how to develop your skillset and become a ...
Java Rockstar?

Subscribe to our newsletter to start Rocking right now!

To get you started we give you two of our best selling eBooks for FREE!

Get ready to Rock!
You can download the complementary eBooks using the links below:
Close