Are you interested in automated testing? If so, become a member of the Java Testing Society!

Integration Testing of Spring MVC Applications: REST API, Part Two

Blue puzzle

This is the fifth part of my spring-test-mvc tutorial and it describes the integration testing of a REST API that is implemented by using Spring MVC 3.1. During this tutorial we will continue writing integration tests for a simple REST API that provides CRUD functions for todo entries. This blog entry concentrates on two controller methods that are used to add new todo entries and update the information of existing todo entries.

This blog entry assumes that we are familiar with the concepts described in the first and second (Spring Test DBUnit configuration and usage) part of this tutorial.

Lets get started by taking a brief look at the implementation of our example application.

The Anatomy of Our Todo Application

Before we can write integration tests for our todo application, we have to know how it is implemented. This section describes the following parts of our example application:

  • The domain model of the todo application.
  • The data transfer objects that are used to transfer information between the REST API and its clients.
  • The service interface that states the contract between the service layer of the todo application and the controller class.
  • The controller that implements the REST API.

These parts are explained with more details in the following.

The Domain Model

The domain model of our todo application consists of a single entity Todo. The source code of the Todo class looks as follows:

import org.hibernate.annotations.Type;
import org.joda.time.DateTime;

import javax.persistence.*;

@Entity
@Table(name="todos")
public class Todo {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(name = "creation_time", nullable = false)
    @Type(type="org.jadira.usertype.dateandtime.joda.PersistentDateTime")
    private DateTime creationTime;

    @Column(name = "description", nullable = true, length = MAX_LENGTH_DESCRIPTION)
    private String description;

    @Column(name = "modification_time", nullable = false)
    @Type(type="org.jadira.usertype.dateandtime.joda.PersistentDateTime")
    private DateTime modificationTime;

    @Column(name = "title", nullable = false, length = MAX_LENGTH_TITLE)
    private String title;

    @Version
    private long version;

    public Todo() {

    }

	//Getters and other methods
}

The Data Transfer Objects

Our todo application has three data transfer objects that are described in the following:

  • The TodoDTO class contains the information of a todo entry.
  • The FieldValidationErrorDTO class contains the information of a single validation error.
  • The FormValidationErrorDTO class is a wrapper class that contains a list of validation errors

Lets move on and take a closer look at these data transfer objects.

The TodoDTO Class

The TodoDTO class is a data transfer object which is used to transfer the information of todo entries between the REST API and its clients. The implementation of this DTO is very simple, and its only methods are getters and setters. It also declares the following validation rules:

  • The title of an todo entry cannot be empty.
  • The maximum length of the todo entry’s title is 100 characters.
  • The maximum length of the todo entry’s description is 500 characters.

The source code of the TodoDTO class 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;

    public TodoDTO() {

    }

	//Getters and setters
}

The FieldValidationErrorDTO Class

The FieldValidationErrorDTO class is a data transfer object that contains the information of a single validation error. This class has two properties that described in the following:

  • The path property identifies the property of a todo entry that did not pass the validation phase.
  • The message property contains the actual validation error message.

The source code of the FieldValidationErrorDTO looks as follows:

public class FieldValidationErrorDTO {

    private String path;
    private String message;

    public FieldValidationErrorDTO(String path, String message) {
        this.path = path;
        this.message = message;
    }

	//Getters
}

The FormValidationErrorDTO Class

The FormValidationErrorDTO class is basically a wrapper class that contains a list of FieldValidationErrorDTO objects. Its source code looks as follows:

public class FormValidationErrorDTO {

    private List<FieldValidationErrorDTO> fieldErrors = new ArrayList<FieldValidationErrorDTO>();

    public FormValidationErrorDTO() {

    }

    public void addFieldError(String path, String message) {
        FieldValidationErrorDTO fieldError = new FieldValidationErrorDTO(path, message);
        fieldErrors.add(fieldError);
    }

    //Getter
}

The Service Interface

The TodoService interface describes the contract between our controller and the service layer. This interface declares two methods that are described in the following:

  • The Todo add(TodoDTO added) method adds a new todo entry and returns the added todo entry.
  • The Todo update(TodoDTO updated) method updates the information of a todo entry and returns the updated todo entry. If no todo entry is found, this method throws a TodoNotFoundException.

The source code of the TodoService interface looks as follows:

public interface TodoService {

    public Todo add(TodoDTO added);

    public Todo update(TodoDTO updated) throws TodoNotFoundException;
}

The Web Layer

The section describes the web layer of our todo application. To be more exact, this section describes both the custom exception that is thrown when the validation fails and the implementation of our controller.

The FormValidationError Class

The FormValidationError class is an exception that is thrown by our controller if the validation of the added or updated todo entry fails. This class contains a list of FieldError objects and its source code looks as follows:

import org.springframework.validation.FieldError;

import java.util.List;

public class FormValidationError extends Exception {

    private List<FieldError> fieldErrors;

    public FormValidationError(List<FieldError> fieldErrors) {
        this.fieldErrors = fieldErrors;
    }

    //Getter
}

The Controller

Our controller class is called TodoController and its methods are described in the following:

  • The TodoDTO add(@RequestBody TodoDTO dto) method adds a new todo entry and returns the information of the added todo entry.
  • The TodoDTO update(@RequestBody TodoDTO dto, @PathVariable(“id”) Long todoId) method updates the information of a todo entry and returns the updated information.
  • The void handleTodoNotFoundException(TodoNotFoundException ex) method is an exception handler method that returns 404 HTTP status code when a TodoNotFoundException is thrown from the service layer of our todo application.
  • The FormValidationErrorDTO handleFormValidationError(FormValidationError validationError) method is an exception handler method that handles validation errors. It returns 400 HTTP status code. The actual validation errors are added to a new FormValidationErrorDTO object that is also returned.

The source code of the TodoController class looks as follows:

import org.springframework.context.MessageSource;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.Validator;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.util.List;
import java.util.Locale;

@Controller
public class TodoController {

    @Resource
    private TodoService service;

    @Resource
    private LocaleContextHolderWrapper localeHolderWrapper;

    @Resource
    private MessageSource messageSource;

    @Resource
    private Validator validator;


    @RequestMapping(value = "/api/todo", method = RequestMethod.POST)
    @ResponseBody
    public TodoDTO add(@RequestBody TodoDTO dto) throws FormValidationError {
        validate("todo", dto);

        Todo added = service.add(dto);

        return createDTO(added);
    }

    @RequestMapping(value = "/api/todo/{id}", method = RequestMethod.PUT)
    @ResponseBody
    public TodoDTO update(@RequestBody TodoDTO dto, @PathVariable("id") Long todoId) throws TodoNotFoundException, FormValidationError {
        validate("todo", dto);

        Todo updated = service.update(dto);

        return createDTO(updated);
    }

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

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

        return dto;
    }

    private void validate(String objectName, Object validated) throws FormValidationError {
        BeanPropertyBindingResult bindingResult = new BeanPropertyBindingResult(validated, objectName);
        validator.validate(validated, bindingResult);

        if (bindingResult.hasErrors()) {
            throw new FormValidationError(bindingResult.getFieldErrors());
        }
    }

    @ExceptionHandler(FormValidationError.class)
    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public FormValidationErrorDTO handleFormValidationError(FormValidationError validationError) {
        Locale current = localeHolderWrapper.getCurrentLocale();

        List<FieldError> fieldErrors = validationError.getFieldErrors();

        FormValidationErrorDTO dto = new FormValidationErrorDTO();

        for (FieldError fieldError: fieldErrors) {
            String[] fieldErrorCodes = fieldError.getCodes();
            for (int index = 0; index < fieldErrorCodes.length; index++) {
                String fieldErrorCode = fieldErrorCodes[index];

                String localizedError = messageSource.getMessage(fieldErrorCode, fieldError.getArguments(), current);
                if (localizedError != null && !localizedError.equals(fieldErrorCode)) {
                    dto.addFieldError(fieldError.getField(), localizedError);
                    break;
                }
                else {
                    if (isLastFieldErrorCode(index, fieldErrorCodes)) {
                        dto.addFieldError(fieldError.getField(), localizedError);
                    }
                }
            }
        }

        return dto;
    }

    private boolean isLastFieldErrorCode(int index, String[] fieldErrorCodes) {
        return index == fieldErrorCodes.length - 1;
    }

    @ExceptionHandler(TodoNotFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public void handleTodoNotFoundException(TodoNotFoundException ex) {
    }
}

Writing Integration Tests

This section describes how we can write integration tests for our REST API. Lets start by taking a look at common testing utilities which we will use in our integration tests.

Common Testing Utilities

We use three testing utilities in our integration tests. These utilities are described in the following:

  • The TodoTestUtil class is used in the unit and integration tests of our todo application.
  • The IntegrationTestUtil class is used in our integration tests.
  • The DBUnit dataset file is used to initialize our database to a known state before our tests are run.

These utilities are described with more details in the following.

The TodoTestUtil Class

The TodoTestUtil class has two static methods which are used in our integration tests. These methods are described in the following:

  • The TodoDTO createDTO(Long id, String description, String title) method creates new TodoDTO objects.
  • The String createStringWithLength(int length) methods creates new String objects.

The source code of the TodoTestUtil looks as follows:

public class TodoTestUtil {

    public static TodoDTO createDTO(Long id, String description, String title) {
        TodoDTO dto = new TodoDTO();

        dto.setId(id);
        dto.setDescription(description);
        dto.setTitle(title);

        return dto;
    }

    public static String createStringWithLength(int length) {
        StringBuilder builder = new StringBuilder();

        for (int index = 0; index < length; index++) {
            builder.append("a");
        }

        return builder.toString();
    }
}

The IntegrationTestUtil Class

The IntegrationTestUtil class has two purposes:

First, It declares a constant called APPLICATION_JSON_UTF8 that is later used to set the content type and character set of HTTP requests and verify the content type and the character set of HTTP responses.

Second, It contains a byte[] convertObjectToJsonBytes(Object object) method that is used to convert objects to bytes that contains the JSON representation of the object given as a parameter. The implementation of this method has following steps:

  1. Create a new ObjectMapper object.
  2. Configure the created object to include only non null properties of the serialized object.
  3. Convert the object as json string and return the created string as byte array.

The source code of the IntegrationTestUtil class looks as follows:

import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.annotate.JsonSerialize;
import org.springframework.http.MediaType;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public class IntegrationTestUtil {

    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(JsonSerialize.Inclusion.NON_NULL);
        return mapper.writeValueAsBytes(object);
    }
}

The DBUnit Dataset

Each integration test use the same DBUnit dataset file to initialize the database to a known state before tests are run. The name of our dataset file is toDoData.xml and its content looks as follows:

<dataset>
    <todos id="1" creation_time="2012-10-21 11:13:28" description="Lorem ipsum" modification_time="2012-10-21 11:13:28" title="Foo" version="0"/>
    <todos id="2" creation_time="2012-10-21 11:13:28" description="Lorem ipsum" modification_time="2012-10-21 11:13:28" title="Bar" version="0"/>
</dataset>

Add Todo Entry

As we know, the add() method of the TodoController class is used to add new todo entries. We have to write three integration tests for this method:

  • We must write a test that ensures the we cannot add an empty todo entry.
  • We must write a test that ensures that we cannot add todo entry when its title and description are too long.
  • We must write a test that ensures that we can add todo entries.

These tests are explained with more details in the following.

Add Empty Todo Entry

We can write the first integration test by following these steps:

  1. Use the @ExpectedDatabase annotation to ensure that no changes are made to the database.
  2. Create a new TodoDTO object with empty title and description.
  3. Perform 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 ‘UTF8′. Transform the created object into a correct format and send it in the body the request.
  4. Verify that the 400 HTTP status code is returned.
  5. Verify that the content type of the response is ‘application/json’ and its character set is ‘UTF8′.
  6. Verify that the body of the response contains a validation error about empty title.

The source code of this integration test looks as follows:

import com.github.springtestdbunit.DbUnitTestExecutionListener;
import com.github.springtestdbunit.annotation.DatabaseSetup;
import com.github.springtestdbunit.annotation.ExpectedDatabase;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.test.web.server.MockMvc;
import org.springframework.test.web.server.samples.context.WebContextLoader;

import static org.springframework.test.web.server.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.server.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.server.result.MockMvcResultMatchers.status;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader = WebContextLoader.class, classes = {ExampleApplicationContext.class})
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class,
        DirtiesContextTestExecutionListener.class,
        TransactionalTestExecutionListener.class,
        DbUnitTestExecutionListener.class})
@DatabaseSetup("toDoData.xml")
public class ITTodoControllerTest {

    //Add web application context here

    private MockMvc mockMvc;

    //Add setUp() method here

    @Test
    @ExpectedDatabase("toDoData.xml")
    public void addEmptyTodo() throws Exception {
        TodoDTO added = TodoTestUtil.createDTO(null, "", "");
        mockMvc.perform(post("/api/todo")
                .contentType(IntegrationTestUtil.APPLICATION_JSON_UTF8)
                .body(IntegrationTestUtil.convertObjectToJsonBytes(added))
        )
                .andExpect(status().isBadRequest())
                .andExpect(content().mimeType(IntegrationTestUtil.APPLICATION_JSON_UTF8))
                .andExpect(content().string("{\"fieldErrors\":[{\"path\":\"title\",\"message\":\"The title cannot be empty.\"}]}"));
    }
}

Add Todo Entry with Validation Errors

We can write the second integration test by following these steps:

  1. Use the @ExpectedDatabase annotation to ensure that no changes are made to the database.
  2. Create the title and description of the todo entry.
  3. Create a new TodoDTO object, and set its title and description.
  4. Perform 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 ‘UTF8′. Transform the created object into a correct format and send it in the body of the request.
  5. Verify that the 400 HTTP status code is returned.
  6. Verify that the content type of the response is ‘application/json’ and its character set is ‘UTF8′.
  7. Verify that the response body contains validation errors for both title and description fields.

The source code of our integration test looks as follows:

import com.github.springtestdbunit.DbUnitTestExecutionListener;
import com.github.springtestdbunit.annotation.DatabaseSetup;
import com.github.springtestdbunit.annotation.ExpectedDatabase;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.test.web.server.MockMvc;
import org.springframework.test.web.server.samples.context.WebContextLoader;

import static org.hamcrest.Matchers.*;
import static org.springframework.test.web.server.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.server.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.server.result.MockMvcResultMatchers.status;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader = WebContextLoader.class, classes = {ExampleApplicationContext.class})
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class,
        DirtiesContextTestExecutionListener.class,
        TransactionalTestExecutionListener.class,
        DbUnitTestExecutionListener.class})
@DatabaseSetup("toDoData.xml")
public class ITTodoControllerTest {

    //Add web application context here

    private MockMvc mockMvc;

    //Add setUp() method here

    @Test
    @ExpectedDatabase("toDoData.xml")
    public void addTodoWhenTitleAndDescriptionAreTooLong() throws Exception {
        String title = TodoTestUtil.createStringWithLength(101);
        String description = TodoTestUtil.createStringWithLength(501);
        TodoDTO added = TodoTestUtil.createDTO(null, description, title);

        mockMvc.perform(post("/api/todo")
                .contentType(IntegrationTestUtil.APPLICATION_JSON_UTF8)
                .body(IntegrationTestUtil.convertObjectToJsonBytes(added))
        )
                .andExpect(status().isBadRequest())
                .andExpect(content().mimeType(IntegrationTestUtil.APPLICATION_JSON_UTF8))
                .andExpect(content().string(startsWith("{\"fieldErrors\":[")))
                .andExpect(content().string(allOf(
                        containsString("{\"path\":\"description\",\"message\":\"The maximum length of the description is 500 characters.\"}"),
                        containsString("{\"path\":\"title\",\"message\":\"The maximum length of the title is 100 characters.\"}")
                )))
                .andExpect(content().string(endsWith("]}")));
    }
}

Add Todo Entry

We can write the third integration test by following these steps:

  1. Use the @ExpectedDatabase annotation to ensure that a new todo entry is added to the used database.
  2. Create a new TodoDTO object with valid title and description.
  3. Perform 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 ‘UTF8′. Transform the created object into a correct format and send it in the body of the request.
  4. Verify that the 200 HTTP status code is returned.
  5. Verify that the content type of the response is ‘application/json’ and its character set is ‘UTF8′.
  6. Verify that the information of the added todo entry is returned.

The source code of our integration test looks as follows:

import com.github.springtestdbunit.DbUnitTestExecutionListener;
import com.github.springtestdbunit.annotation.DatabaseSetup;
import com.github.springtestdbunit.annotation.ExpectedDatabase;
import com.github.springtestdbunit.assertion.DatabaseAssertionMode;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.test.web.server.MockMvc;
import org.springframework.test.web.server.samples.context.WebContextLoader;

import static org.springframework.test.web.server.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.server.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.server.result.MockMvcResultMatchers.status;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader = WebContextLoader.class, classes = {ExampleApplicationContext.class})
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class,
        DirtiesContextTestExecutionListener.class,
        TransactionalTestExecutionListener.class,
        DbUnitTestExecutionListener.class})
@DatabaseSetup("toDoData.xml")
public class ITTodoControllerTest {

    //Add web application context here

    private MockMvc mockMvc;

    //Add setUp() method here

    @Test
    @ExpectedDatabase(value="toDoData-add-expected.xml", assertionMode = DatabaseAssertionMode.NON_STRICT)
    public void add() throws Exception {
        TodoDTO added = TodoTestUtil.createDTO(null, "description", "title");
        mockMvc.perform(post("/api/todo")
                .contentType(IntegrationTestUtil.APPLICATION_JSON_UTF8)
                .body(IntegrationTestUtil.convertObjectToJsonBytes(added))
        )
                .andExpect(status().isOk())
                .andExpect(content().mimeType(IntegrationTestUtil.APPLICATION_JSON_UTF8))
                .andExpect(content().string("{\"id\":3,\"description\":\"description\",\"title\":\"title\"}"));
    }
}

The name of the DBUnit dataset file that is used to verify that the todo entry was added is toDoData-add-expected.xml. The content of this file looks as follows:

<dataset>
    <todos id="1" description="Lorem ipsum" title="Foo" version="0"/>
    <todos id="2" description="Lorem ipsum" title="Bar" version="0"/>
    <todos id="3" description="description" title="title" version="0"/>
</dataset>

Update Todo Entry

As we learned earlier, the controller method that is used to update the information of existing todo entries is called update(). We have to write four integration tests for this method:

  • We have to write a test that ensures that we cannot update empty todo entry.
  • We have to write a test that ensures that we cannot update the information of a todo entry when its title and description are too long.
  • We have to write a test that ensures that 404 HTTP status code is returned when the updated todo entry is not found.
  • We have to write a test that ensures that we can update the information of a todo entry.

These tests are described with more details in the following:

Update Empty Todo Entry

We can write the first integration test by following these steps:

  1. Use the @ExpectedDatabase annotation to verify that no changes are made to the database.
  2. Create a new TodoDTO object and set the value of its id.
  3. Perform a PUT request to url ‘/api/todo/1′. Set the content type of the request to ‘application/json’. Set the character set of the request to ‘UTF8′. Transform the created object into a correct format and send it in the body the request.
  4. Verify that the 400 HTTP status code is returned.
  5. Verify that the content type of the response is ‘application/json’ and its character set is ‘UTF8′.
  6. Verify that the body of the response contains a validation error about empty title.

The source code of our integration test looks as follows:

import com.github.springtestdbunit.DbUnitTestExecutionListener;
import com.github.springtestdbunit.annotation.DatabaseSetup;
import com.github.springtestdbunit.annotation.ExpectedDatabase;
import com.github.springtestdbunit.assertion.DatabaseAssertionMode;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.test.web.server.MockMvc;
import org.springframework.test.web.server.samples.context.WebContextLoader;

import static org.springframework.test.web.server.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.server.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.server.result.MockMvcResultMatchers.status;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader = WebContextLoader.class, classes = {ExampleApplicationContext.class})
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class,
        DirtiesContextTestExecutionListener.class,
        TransactionalTestExecutionListener.class,
        DbUnitTestExecutionListener.class})
@DatabaseSetup("toDoData.xml")
public class ITTodoControllerTest {

    //Add web application context here

    private MockMvc mockMvc;

    //Add setUp() method here

    @Test
    @ExpectedDatabase("toDoData.xml")
    public void updateEmptyTodo() throws Exception {
        TodoDTO updated = TodoTestUtil.createDTO(1L, "", "");

        mockMvc.perform(put("/api/todo/{id}", 1L)
                .contentType(IntegrationTestUtil.APPLICATION_JSON_UTF8)
                .body(IntegrationTestUtil.convertObjectToJsonBytes(updated))
        )
                .andExpect(status().isBadRequest())
                .andExpect(content().mimeType(IntegrationTestUtil.APPLICATION_JSON_UTF8))
                .andExpect(content().string("{\"fieldErrors\":[{\"path\":\"title\",\"message\":\"The title cannot be empty.\"}]}"));
    }
}

Update Todo Entry with Validation Errors

We can write the second integration test by following these steps:

  1. Use the @ExpectedDatabase annotation to verify that no changes are made to the database.
  2. Create the title and description of the todo entry.
  3. Create a new TodoDTO object and set the value of its id, title and description.
  4. Perform a PUT request to url ‘/api/todo/1′. Set the content type of the request to ‘application/json’. Set the character set of the request to ‘UTF8′. Transform the created object into a correct format and send it in the body the request.
  5. Verify that the 400 HTTP status code is returned.
  6. Verify that the content type of the response is ‘application/json’ and its character set is ‘UTF8′.
  7. Verify that the body of the response contains a validation error about empty title.

The source code of our integration test looks as follows:

import com.github.springtestdbunit.DbUnitTestExecutionListener;
import com.github.springtestdbunit.annotation.DatabaseSetup;
import com.github.springtestdbunit.annotation.ExpectedDatabase;
import com.github.springtestdbunit.assertion.DatabaseAssertionMode;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.test.web.server.MockMvc;
import org.springframework.test.web.server.samples.context.WebContextLoader;

import static org.hamcrest.Matchers.*;
import static org.springframework.test.web.server.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.server.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.server.result.MockMvcResultMatchers.status;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader = WebContextLoader.class, classes = {ExampleApplicationContext.class})
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class,
        DirtiesContextTestExecutionListener.class,
        TransactionalTestExecutionListener.class,
        DbUnitTestExecutionListener.class})
@DatabaseSetup("toDoData.xml")
public class ITTodoControllerTest {

    //Add web application context here

    private MockMvc mockMvc;

    //Add setUp() method here

    @Test
    @ExpectedDatabase("toDoData.xml")
    public void updateTodoWhenTitleAndDescriptionAreTooLong() throws Exception {
        String title = TodoTestUtil.createStringWithLength(101);
        String description = TodoTestUtil.createStringWithLength(501);

        TodoDTO updated = TodoTestUtil.createDTO(1L, description, title);

        mockMvc.perform(put("/api/todo/{id}", 1L)
                .contentType(IntegrationTestUtil.APPLICATION_JSON_UTF8)
                .body(IntegrationTestUtil.convertObjectToJsonBytes(updated))
        )
                .andExpect(status().isBadRequest())
                .andExpect(content().mimeType(IntegrationTestUtil.APPLICATION_JSON_UTF8))
                .andExpect(content().string(startsWith("{\"fieldErrors\":[")))
                .andExpect(content().string(allOf(
                        containsString("{\"path\":\"description\",\"message\":\"The maximum length of the description is 500 characters.\"}"),
                        containsString("{\"path\":\"title\",\"message\":\"The maximum length of the title is 100 characters.\"}")
                )))
                .andExpect(content().string(endsWith("]}")));
    }
}

Update Todo Entry when Todo Entry Is Not Found

We can write the third integration test by following these steps:

  1. Use the @ExpectedDatabase annotation to verify that no changes are made to the database.
  2. Create a new TodoDTO object and set the value of its id, title and description. Ensure that no todo entry is found with the given id.
  3. Perform a PUT request to url ‘/api/todo/3′. Set the content type of the request to ‘application/json’. Set the character set of the request to ‘UTF8′. Transform the created object into a correct format and send it in the body the request.
  4. Verify that the 404 HTTP status code is returned.

The source code of our integration test looks as follows:

import com.github.springtestdbunit.DbUnitTestExecutionListener;
import com.github.springtestdbunit.annotation.DatabaseSetup;
import com.github.springtestdbunit.annotation.ExpectedDatabase;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.test.web.server.MockMvc;
import org.springframework.test.web.server.samples.context.WebContextLoader;

import static org.springframework.test.web.server.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.server.result.MockMvcResultMatchers.status;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader = WebContextLoader.class, classes = {ExampleApplicationContext.class})
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class,
        DirtiesContextTestExecutionListener.class,
        TransactionalTestExecutionListener.class,
        DbUnitTestExecutionListener.class})
@DatabaseSetup("toDoData.xml")
public class ITTodoControllerTest {

    //Add web application context here

    private MockMvc mockMvc;

    //Add setUp() method here

    @Test
    @ExpectedDatabase("toDoData.xml")
    public void updateTodoWhenTodoIsNotFound() throws Exception {
        TodoDTO updated = TodoTestUtil.createDTO(3L, "description", "title");

        mockMvc.perform(put("/api/todo/{id}", 3L)
                .contentType(IntegrationTestUtil.APPLICATION_JSON_UTF8)
                .body(IntegrationTestUtil.convertObjectToJsonBytes(updated))
        )
                .andExpect(status().isNotFound());
    }
}

Update Todo Entry

We can write the fourth integration test by following these steps:

  1. Use the @ExpectedDatabase annotation to verify that the information of the todo entry is updated.
  2. Create a new TodoDTO object and set the value of its id, title and description.
  3. Perform a PUT request to url ‘/api/todo/1′. Set the content type of the request to ‘application/json’. Set the character set of the request to ‘UTF8′. Transform the created object into a correct format and send it in the body the request.
  4. Verify that the 200 HTTP status code is returned.
  5. Verify that the content type of the response is ‘application/json’ and its character set is ‘UTF8′.
  6. Verify that the information of the updated todo entry is returned.

The source code of our integration test looks as follows:

import com.github.springtestdbunit.DbUnitTestExecutionListener;
import com.github.springtestdbunit.annotation.DatabaseSetup;
import com.github.springtestdbunit.annotation.ExpectedDatabase;
import com.github.springtestdbunit.assertion.DatabaseAssertionMode;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.test.web.server.MockMvc;
import org.springframework.test.web.server.samples.context.WebContextLoader;

import static org.springframework.test.web.server.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.server.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.server.result.MockMvcResultMatchers.status;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader = WebContextLoader.class, classes = {ExampleApplicationContext.class})
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class,
        DirtiesContextTestExecutionListener.class,
        TransactionalTestExecutionListener.class,
        DbUnitTestExecutionListener.class})
@DatabaseSetup("toDoData.xml")
public class ITTodoControllerTest {

    //Add web application context here

    private MockMvc mockMvc;

    //Add setUp() method here

    @Test
    @ExpectedDatabase(value="toDoData-update-expected.xml", assertionMode = DatabaseAssertionMode.NON_STRICT)
    public void update() throws Exception {
        TodoDTO updated = TodoTestUtil.createDTO(1L, "description", "title");

        mockMvc.perform(put("/api/todo/{id}", 1L)
                .contentType(IntegrationTestUtil.APPLICATION_JSON_UTF8)
                .body(IntegrationTestUtil.convertObjectToJsonBytes(updated))
        )
                .andExpect(status().isOk())
                .andExpect(content().mimeType(IntegrationTestUtil.APPLICATION_JSON_UTF8))
                .andExpect(content().string("{\"id\":1,\"description\":\"description\",\"title\":\"title\"}"));
    }
}

The name of the DBUnit dataset file that is used to verify that the information of the todo entry was updated is toDoData-update-expected.xml. The content of this file looks as follows:

<dataset>
    <todos id="1" description="description" title="title" version="1"/>
    <todos id="2" description="Lorem ipsum" title="Bar" version="0"/>
</dataset>

What is Next?

We have now learned how we can write integration tests for a REST API methods that can be used to add information to a database and update the existing information. This tutorial has taught us two things:

  • We know how we can set content type of a HTTP request.
  • We know how we can convert objects into json and send it in the body a HTTP request.

In the next part of this tutorial, we learn to write integration tests which ensure that our application is secured correctly. You might also want to find out how you can clean up your tests by using JsonPath expressions.

P.S. The full source code of our todo application is available at Github.

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 →

2 comments… add one

  • 写的很好,非常感谢!

    Reply
    • You are welcome!

      Reply

Leave a Comment