Do you want to be a better Java developer? If so, check out 10 Books Every Java Developer Should Read.

Integration Testing of Spring MVC Applications: Forms

Share
The final piece of the puzzle

This is the third part of my spring-test-mvc tutorial and it describes how we can write integration tests for controller methods that are processing form submissions.

During this tutorial we will continue writing integration tests for a simple todo application. This tutorial concentrates on two functions that are used to create 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 part of this tutorial.

Getting the Required Dependencies

Our tests use Jackson to convert form objects in to strings that are send in the body of the performed POST request. Thus, we have to declare the Jackson dependencies in our pom.xml file. We can do this by following these steps:

  1. Declare the jackson-core-asl (version 1.9.9) dependency in our pom.xml file.
  2. Declare the jackson-mapper-asl (version 1.9.9) dependency in our pom.xml file.

We can do this by adding the following snippet to our pom.xml file:

<dependency>
    <groupId>org.codehaus.jackson</groupId>
    <artifactId>jackson-core-asl</artifactId>
    <version>1.9.9</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.codehaus.jackson</groupId>
    <artifactId>jackson-mapper-asl</artifactId>
    <version>1.9.9</version>
    <scope>test</scope>
</dependency>

The Anatomy of Our Todo Application

Before we can write integration tests for our todo application, we need to take a quick look at its anatomy. This section describes the used form object, the used service interface and the controller implementation.

The Form Object

The form object of our todo application is rather simple. It has only a few fields, and its only methods are simple 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 Service Interface

If we want to understand the implementation of our controller methods, we have to understand the contract between our controller class and the service layer. This contract is described by the TodoService interface that declares two new methods:

  • The Todo add(TodoDTO added) method adds a new todo entry and returns a the added entry.
  • The Todo update(TodoDTO updated) method updates the information of a single todo entry and returns the updated entry. If no todo entry is found, this method throws 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 Controller

The TodoController class has four methods that processes requests that are related to adding and updating todo entries. These methods are described in the following:

  • The showAddTodoForm() method shows the page that contains the add todo entry form.
  • The add() methods processes the form submissions of the add todo form.
  • The showUpdateTodoForm() method shows the page that contains the update todo entry form.
  • The update() method processes the form submissions of the update todo form.

The source code of the TodoController class looks as follows:

import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import javax.annotation.Resource;
import javax.validation.Valid;

@Controller
@SessionAttributes("todo")
public class TodoController {

    @Resource
    private TodoService service;

    @Resource
    private MessageSource messageSource;

    @RequestMapping(value = "/todo/add", method = RequestMethod.GET)
    public String showAddTodoForm(Model model) {
        TodoDTO formObject = new TodoDTO();
        model.addAttribute("todo", formObject);

        return "todo/add";
    }

    @RequestMapping(value = "/todo/add", method = RequestMethod.POST)
    public String add(@Valid @ModelAttribute("todo") TodoDTO dto, BindingResult result, RedirectAttributes attributes) {
        if (result.hasErrors()) {
            return "todo/add";
        }

        Todo added = service.add(dto);

        addFeedbackMessage(attributes, "feedback.message.todo.added", added.getTitle());
        attributes.addAttribute("id", added.getId());

        return createRedirectViewPath("/todo/{id}");
    }

    @RequestMapping(value = "/todo/update/{id}", method = RequestMethod.GET)
    public String showUpdateTodoForm(@PathVariable("id") Long id, Model model) throws TodoNotFoundException {
        Todo updated = service.findById(id);

        TodoDTO formObject = constructFormObjectForUpdateForm(updated);
        model.addAttribute("todo", formObject);

        return "todo/update";
    }

    @RequestMapping(value = "/todo/update", method = RequestMethod.POST)
    public String update(@Valid @ModelAttribute("todo") TodoDTO dto, BindingResult result, RedirectAttributes attributes) throws TodoNotFoundException {
        if (result.hasErrors()) {
            return "todo/update";
        }

        Todo updated = service.update(dto);

        addFeedbackMessage(attributes, "feedback.message.todo.updated", updated.getTitle());
        attributes.addAttribute("id", updated.getId());

        return createRedirectViewPath("/todo/{id}");
    }

    private TodoDTO constructFormObjectForUpdateForm(Todo updated) {
        TodoDTO dto = new TodoDTO();

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

        return dto;
    }

    private void addFeedbackMessage(RedirectAttributes attributes, String messageCode, Object... messageParameters) {
        String localizedFeedbackMessage = getMessage(messageCode, messageParameters);
        attributes.addFlashAttribute("feedbackMessage", localizedFeedbackMessage);
    }

    private String getMessage(String messageCode, Object... messageParameters) {
        Locale current = LocaleContextHolder.getLocale();
        return messageSource.getMessage(messageCode, messageParameters, current);
    }


    private String createRedirectViewPath(String requestMapping) {
        StringBuilder redirectViewPath = new StringBuilder();
        redirectViewPath.append("redirect:");
        redirectViewPath.append(requestMapping);
        return redirectViewPath.toString();
    }
}

Writing Integration Tests for Forms

The section describes how we can write integration tests for the forms of our todo application. Lets move on and take a look at the common testing utilities that are used in our integration tests.

Common Testing Utilities

We use three testing utilities in our integration tests. These test utilities are:

  • The TodoTestUtil class is used in both the unit tests and integration tests of our todo application.
  • The IntegrationTestUtil class is used only in our integration tests.
  • The toDoData.xml is a DBUnit dataset that initializes the used database to a known state before our tests are run.

These utilities are described with more details in following.

The TodoTestUtil Class

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

  • The TodoDTO createFormObject(Long id, String description, String title) method creates a new form objects.
  • The String createStringWithLength(int length) method creates new String objects.

The source code of the TodoTestUtil class is given in the following:

public class TodoTestUtil {

    public static TodoDTO createFormObject(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 one method that is used in our integration tests. This method converts form objects to strings which mime type is ‘application/x-www-form-urlencoded’. This method is called convertObjectToFormUrlEncodedBytes() and its implementation has following steps:

  1. Create a new ObjectMapper object and configure it to include only non null properties of the serialized object.
  2. Convert the form object to a map that uses the names of its properties as keys.
  3. Get the names of the form object’s properties from the map.
  4. Build the string by iterating the obtained property names.
  5. 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;

public class IntegrationTestUtil {
    public static byte[] convertObjectToFormUrlEncodedBytes(Object object) {
        ObjectMapper mapper = new ObjectMapper();
        mapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_NULL);

        Map<String, Object> propertyValues = mapper.convertValue(object, Map.class);

        Set<String> propertyNames = propertyValues.keySet();
        Iterator<String> nameIter = propertyNames.iterator();

        StringBuilder formUrlEncoded = new StringBuilder();

        for (int index=0; index < propertyNames.size(); index++) {
            String currentKey = nameIter.next();
            Object currentValue = propertyValues.get(currentKey);

            formUrlEncoded.append(currentKey);
            formUrlEncoded.append("=");
            formUrlEncoded.append(currentValue);

            if (nameIter.hasNext()) {
                formUrlEncoded.append("&");
            }
        }

        return formUrlEncoded.toString().getBytes();
    }
}

The DBUnit Dataset

Each integration test uses the same DBUnit dataset which is found from the toDoData.xml. The content of this dataset 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>

Show Add Todo Form

The showAddTodoForm() method of the TodoController class is used to view the page that contains the add todo form. We can write an integration test for this method by following these steps:

  1. Use the @ExpectedDatabase annotation to ensure that no changes are made to the database.
  2. Perform a GET request to url ‘/todo/add’.
  3. Verify that the HTTP status code is 200.
  4. Verify that name of the rendered view is ‘todo/add’.
  5. Verify that the request is forwarded to url ‘/WEB-INF/jsp/todo/add.jsp’.
  6. Verify that each field of our form object is empty.

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.get;
import static org.springframework.test.web.server.result.MockMvcResultMatchers.*;

@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 showAddTodoForm() throws Exception {
        mockMvc.perform(get("/todo/add"))
                .andExpect(status().isOk())
                .andExpect(view().name("todo/add"))
                .andExpect(forwardedUrl("/WEB-INF/jsp/todo/add.jsp"))
                .andExpect(model().attribute("todo", hasProperty("id", nullValue())))
                .andExpect(model().attribute("todo", hasProperty("description", isEmptyOrNullString())))
                .andExpect(model().attribute("todo", hasProperty("title", isEmptyOrNullString())));
    }
}

Add Todo

The add() method of the TodoController class is responsible of processing the form submissions of the add todo form. We have to write three integration tests for this method. These tests are described in following:

  • We have to write a test that ensures that the method is working correctly when an empty add todo form is submitted.
  • We have to write a test that ensures that the method is working properly when the title and the description of the todo entry are too long, and the add todo form is submitted.
  • We have to write a test that ensures that this method is working correctly when a new todo entry is added.

These tests are described with more details in following.

Submit an Empty Add Todo Form

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.
  3. Perform a POST request to url ‘/todo/add’. Set the content type of the request body to ‘application/x-www-form-urlencoded’. Transform the form object to the correct format and send it in the body of the post request. Set the form object to the used session.
  4. Verify that the HTTP status code is 200.
  5. Verify that the name of the rendered view is ‘/todo/add’.
  6. Verify that the request is forwarded to url ‘/WEB-INF/jsp/todo/add.jsp’.
  7. Verify that there is a field error in the title field.
  8. Verify that our form object is empty.

The source code of our first 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.http.MediaType;
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.post;
import static org.springframework.test.web.server.result.MockMvcResultMatchers.*;

@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 formObject = new TodoDTO();
        mockMvc.perform(post("/todo/add")
                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                .body(IntegrationTestUtil.convertObjectToFormUrlEncodedBytes(formObject))
                .sessionAttr("todo", formObject)
        )
                .andExpect(status().isOk())
                .andExpect(view().name("todo/add"))
                .andExpect(forwardedUrl("/WEB-INF/jsp/todo/add.jsp"))
                .andExpect(model().attributeHasFieldErrors("todo", "title"))
                .andExpect(model().attribute("todo", hasProperty("id", nullValue())))
                .andExpect(model().attribute("todo", hasProperty("description", isEmptyOrNullString())))
                .andExpect(model().attribute("todo", hasProperty("title", isEmptyOrNullString())));
    }
}

Submit Add Todo Form 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 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 ‘/todo/add’. Set the content type of the request body to ‘application/x-www-form-urlencoded’. Transform the form object to the correct format and send it in the body of the post request. Set the form object to the used session.
  5. Verify that the HTTP status code is 200.
  6. Verify that the name of the rendered view is ‘/todo/add’.
  7. Verify that the request is forwarded to url ‘/WEB-INF/jsp/todo/add.jsp’.
  8. Verify that there are a field errors in the title and description fields.
  9. Verify that our form object contains the correct values.

The source code of our second 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.http.MediaType;
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.post;
import static org.springframework.test.web.server.result.MockMvcResultMatchers.*;

@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 formObject = TodoTestUtil.createFormObject(null, description, title);

        mockMvc.perform(post("/todo/add")
                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                .body(IntegrationTestUtil.convertObjectToFormUrlEncodedBytes(formObject))
                .sessionAttr("todo", formObject)
        )
                .andExpect(status().isOk())
                .andExpect(view().name("todo/add"))
                .andExpect(forwardedUrl("/WEB-INF/jsp/todo/add.jsp"))
                .andExpect(model().attributeHasFieldErrors("todo", "title"))
                .andExpect(model().attributeHasFieldErrors("todo", "description"))
                .andExpect(model().attribute("todo", hasProperty("id", nullValue())))
                .andExpect(model().attribute("todo", hasProperty("description", is(description))))
                .andExpect(model().attribute("todo", hasProperty("title", is(title))));
    }
}

Submit Add Todo Form

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

  1. Use the @ExpectedDatabase annotation to verify that a new todo entry is added to database.
  2. Create a new TodoDTO object that has the correct information.
  3. Perform a POST request to url ‘/todo/add’. Set the content type of the request body to ‘application/x-www-form-urlencoded’. Transform the form object to the correct format and send it in the body of the post request. Set the form object to the used session.
  4. Verify that the HTTP status code is 200.
  5. Verify that the name of the rendered view is ‘redirect:/todo/view/{id}’.
  6. Verify that the model has an attribute caled ‘id’ and that its value is 3.
  7. Verify that the correct feedback message is given as a flash attribute.

The source code of our third 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.http.MediaType;
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.is;
import static org.springframework.test.web.server.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.server.result.MockMvcResultMatchers.*;

@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 addTodo() throws Exception {
        TodoDTO formObject = TodoTestUtil.createFormObject(null, "description", "title");

        mockMvc.perform(post("/todo/add")
                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                .body(IntegrationTestUtil.convertObjectToFormUrlEncodedBytes(formObject))
                .sessionAttr("todo", formObject)
        )
                .andExpect(status().isOk())
                .andExpect(view().name("redirect:/todo/view/{id}"))
                .andExpect(model().attribute("id", is("3")))
                .andExpect(flash().attribute("feedbackMessage", is("Todo entry: title was added.")));
    }
}

The name of the used DBUnit dataset is toDoData-add-expected.xml and its content is given in the following:

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

Show Update Todo Form

The showUpdateTodoForm() method of the TodoController class views the page that contains the update todo form. We have to write two integration tests for this method. These tests are described in the following:

  • We have to write a test that ensures that this method is working correctly when the updated todo entry is found.
  • We have to write a test that ensures that this method is working properly when the updated todo entry is not found.

These tests are described with more details in following.

Show Update Todo Form

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. Perform a GET request to url ‘/todo/update/1′.
  3. Verify that the HTTP status code is 200.
  4. Verify that the name of the rendered view is ‘todo/update’.
  5. Verify that the request is forwarded to url ‘/WEB-INF/jsp/todo/update.jsp’.
  6. Verify that our form object contains the correct information.

The source code of our first 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.get;
import static org.springframework.test.web.server.result.MockMvcResultMatchers.*;

@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 showUpdateTodoForm() throws Exception {
        mockMvc.perform(get("/todo/update/{id}", 1L))
                .andExpect(status().isOk())
                .andExpect(view().name("todo/update"))
                .andExpect(forwardedUrl("/WEB-INF/jsp/todo/update.jsp"))
                .andExpect(model().attribute("todo", hasProperty("id", is(1L))))
                .andExpect(model().attribute("todo", hasProperty("description", is("Lorem ipsum"))))
                .andExpect(model().attribute("todo", hasProperty("title", is("Foo"))));
    }
}

Show Update Todo Form When the Todo Entry Is Not Found

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. Perform a GET to request to url ‘/todo/update/3′.
  3. Verify that the HTTP status code is 404.
  4. Verify that the name of the rendered view is ‘error/404′.
  5. Verify that the request is forwarded to url ‘/WEB-INF/jsp/error/404.jsp’.

The source code of our second 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.get;
import static org.springframework.test.web.server.result.MockMvcResultMatchers.*;

@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 showUpdateTodoFormWhenTodoIsNotFound() throws Exception {
        mockMvc.perform(get("/todo/update/{id}", 3L))
                .andExpect(status().isNotFound())
                .andExpect(view().name("error/404"))
                .andExpect(forwardedUrl("/WEB-INF/jsp/error/404.jsp"));
    }
}

Update Todo

The update() method of the TodoController class processes the form submissions of the update todo form. We have to write four integration tests for this method. These integration tests are described in the following:

  • We have to write an integration test that ensures that the method is working properly when an empty update todo form is submitted.
  • We have to write an integration test that ensures the method is working properly when the title and description of the todo entry are too long, and the update todo form is submitted.
  • We have to write an integration test that ensures that the method is working correctly when the information of a todo entry is updated.
  • We have to write an integration test that ensures that the method is working properly when the updated todo entry is not found.

These tests are described with more details in following.

Submit an Empty Update Todo Form

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 TodoDTO object and set the value of its id field.
  3. Perform a POST request to url ‘/todo/update’. Set the content type of the request body to ‘application/x-www-form-urlencoded’. Transform the form object to the correct format and send it in the body of the post request. Set form object to the used session.
  4. Verify that the HTTP status code is 200.
  5. Verify that the name of the rendered view is ‘/todo/update’.
  6. Verify that the request is forwarded to url ‘/WEB-INF/jsp/todo/update.jsp’.
  7. Verify that there is a field error in the title field.
  8. Verify that our form object is empty.

The source code of our first 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.http.MediaType;
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.post;
import static org.springframework.test.web.server.result.MockMvcResultMatchers.*;

@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 formObject = TodoTestUtil.createFormObject(1L, null, null);
        mockMvc.perform(post("/todo/update")
                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                .body(IntegrationTestUtil.convertObjectToFormUrlEncodedBytes(formObject))
                .sessionAttr("todo", formObject)
        )
                .andExpect(status().isOk())
                .andExpect(view().name("todo/update"))
                .andExpect(forwardedUrl("/WEB-INF/jsp/todo/update.jsp"))
                .andExpect(model().attributeHasFieldErrors("todo", "title"))
                .andExpect(model().attribute("todo", hasProperty("id", is(1L))))
                .andExpect(model().attribute("todo", hasProperty("description", isEmptyOrNullString())))
                .andExpect(model().attribute("todo", hasProperty("title", isEmptyOrNullString())));
    }
}

Submit Update Todo Form 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 the description of the todo entry.
  3. Create a new TodoDTO object and set its id, title and description.
  4. Perform a POST request to url ‘/todo/update’. Set the content type of the request body to ‘application/x-www-form-urlencoded’. Transform the form object to the correct format and send it in the body of the post request. Set form object to the used session.
  5. Verify that the HTTP status code is 200.
  6. Verify that the name of the rendered view is ‘/todo/update’.
  7. Verify that the request is forwarded to url ‘/WEB-INF/jsp/todo/update.jsp’.
  8. Verify that there are field errors in the title and description fields.
  9. Verify that our form object contains the correct values.

The source code of our second 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.http.MediaType;
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.post;
import static org.springframework.test.web.server.result.MockMvcResultMatchers.*;

@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 formObject = TodoTestUtil.createFormObject(1L, description, title);

        mockMvc.perform(post("/todo/update")
                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                .body(IntegrationTestUtil.convertObjectToFormUrlEncodedBytes(formObject))
                .sessionAttr("todo", formObject)
        )
                .andExpect(status().isOk())
                .andExpect(view().name("todo/update"))
                .andExpect(forwardedUrl("/WEB-INF/jsp/todo/update.jsp"))
                .andExpect(model().attributeHasFieldErrors("todo", "title"))
                .andExpect(model().attributeHasFieldErrors("todo", "description"))
                .andExpect(model().attribute("todo", hasProperty("id", is(1L))))
                .andExpect(model().attribute("todo", hasProperty("description", is(description))))
                .andExpect(model().attribute("todo", hasProperty("title", is(title))));
    }
}

Submit Update Todo Form

We can write the third 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 that has the correct information.
  3. Perform a POST request to url ‘/todo/update’. Set the content type of the request body to ‘application/x-www-form-urlencoded’. Transform the form object to the correct format and send it in the body of the post request. Set form object to the used session.
  4. Verify that the HTTP status code is 200.
  5. Verify that the name of the rendered view is ‘redirect:/todo/view/{id}’.
  6. Verify that the model has an attribute ‘id’ and that its value is 1.
  7. Verify that the correct feedback message is given as a flash attribute.

The source code of our third 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.http.MediaType;
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.is;
import static org.springframework.test.web.server.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.server.result.MockMvcResultMatchers.*;

@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 updateTodo() throws Exception {
        TodoDTO formObject = TodoTestUtil.createFormObject(1L, "description", "title");

        mockMvc.perform(post("/todo/update")
                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                .body(IntegrationTestUtil.convertObjectToFormUrlEncodedBytes(formObject))
                .sessionAttr("todo", formObject)
        )
                .andExpect(status().isOk())
                .andExpect(view().name("redirect:/todo/view/{id}"))
                .andExpect(model().attribute("id", is("1")))
                .andExpect(flash().attribute("feedbackMessage", is("Todo entry: title was updated.")));
    }
}

The name of the used DBUnit dataset is toDoData-update-expected.xml and its content is given in the following:

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

Submit Update Todo Form When the Todo Entry Is Not Found

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

  1. Use the @ExpectedDatabase annotation to verify that no changes is made to the database.
  2. Create a new TodoDTO object and set its id, title and description. Ensure that no todo entry is found with its id.
  3. Perform a POST request to url ‘/todo/update’. Set the content type of the request body to ‘application/x-www-form-urlencoded’. Transform the form object to the correct format and send it in the body of the post request. Set form object to the used session.
  4. Verify that the HTTP status code is 404.
  5. Verify that the name of the rendered view is ‘error/404′.
  6. Verify that the request is forwarded to url ‘/WEB-INF/jsp/error/404.jsp’.

The source code of our fourth 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.http.MediaType;
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.post;
import static org.springframework.test.web.server.result.MockMvcResultMatchers.*;

@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 formObject = TodoTestUtil.createFormObject(3L, "description", "title");

        mockMvc.perform(post("/todo/update")
                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                .body(IntegrationTestUtil.convertObjectToFormUrlEncodedBytes(formObject))
                .sessionAttr("todo", formObject)
        )
                .andExpect(status().isNotFound())
                .andExpect(view().name("error/404"))
                .andExpect(forwardedUrl("/WEB-INF/jsp/error/404.jsp"));
    }
}

Conclusion

We have now learned how we can write integration tests for controller methods that processes form submissions. This tutorial has taught us three things:

  • We know how to set a body of a HTTP request and specify its content type.
  • We know how to add values to the session used in our integration test.
  • We know how we can check that our form submission has field errors.

In the next part of this tutorial, we learn to write integration tests for a REST API that reads information from the database and returns it to a single page web application.

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

If you enjoy reading similar content, you should follow me on Twitter:

About the Author

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

About Petri Kainulainen →

27 comments… add one

  • Thanks a lot!

    Reply
    • You are welcome!

      Reply
  • Very useful, clean and well explained Petri. Really good job!

    Reply
  • Hello ! I’m trying to do what in you site, but I can’t :-(

    http://stackoverflow.com/questions/17073299/bindingresult-doesnt-work-spring-test

    Reply
    • Hi,

      I assume that you are not using JSR-303 validation since you have not annotated the method parameter with the @Valid annotation? Or did you just forget to add it?

      However, since the validation is working when you run your application and not working when you run a test against your code, the configuration of the application and the configuration of your test might be different.

      I would start by checking the configuration files (or classes if you use Java configuration).

      Reply
  • Petri,

    thanks for answer me :-) do you have an email ? I’ve been trying to do my test for 3 days and doesn’t work. I think you can help me.

    thanks

    Reply
  • Thanks a lot my friend. I had forgotten to put @Valid ;-) It’s work now. I’d like to have your email, I like so much your website, book and explanations. Could you send me an email ? Thanks, thanks, thanks !

    Reply
    • You are welcome!

      It is good to hear that you were able to solve this problem.

      Also, you should have a new email message in your inbox.

      Reply
  • Petri,

    I’m sorry but now I have other problem.

    look,
    @Test
    @ExpectedDatabase(“timeSheetControllerIt.xml”)
    public void novoTimeSheetSemHoraFinal() throws Exception {
    TimeSheet novoTimeSheet = new TimeSheet();
    novoTimeSheet.setHoraInicio(calendar.getTime());
    mockMvc.perform(
    post(“/timesheet/addtimesheet”)
    .contentType(MediaType.APPLICATION_FORM_URLENCODED)
    .body(super.convertObjectToFormUrlEncodedBytes(novoTimeSheet))
    .sessionAttr(“timesheet”, novoTimeSheet)
    )
    .andDo(print())
    .andExpect(status().isOk())
    .andExpect(view().name(“timesheetcrud/novo”))
    .andExpect(forwardedUrl(“/WEB-INF/views/timesheetcrud/novo.jsp”))
    .andExpect(model().attributeHasFieldErrors(“timesheet”, “horaFim”))
    .andExpect(model().attribute(“messageHoraFimError”, “timesheetcontroller.horafim.invalida”));
    }

    I’m setting “novoTimeSheet.setHoraInicio(calendar.getTime());”, but, in my controller:

    @RequestMapping(value = “/addtimesheet”, method = RequestMethod.POST)
    public String addTimeSheet(@Valid @ModelAttribute(“timesheet”)TimeSheet timeSheet,
    BindingResult bindingResult,
    ModelMap model) { …

    my attribute is null (timeSheet.getHoraInicio()), in other words, a value of that I’m setting in “novoTimeSheet” does not reach in my controller.

    I don’t know what I’m doing wrong. TimeSheet is my domain with hibernate annotations. I saw that in your example that you’re using DTO.

    thanks again :-)

    Reply
    • The answer to this problem was that the controller class was not annotated with the @SessionAttributes annotation. Maybe this remark will help someone else who is having the same problem.

      Reply
  • Yes, thanks Petri, the @SessionAttributes issue is exactly the problem I was having too! This is a really helpful tutorial.

    Reply
    • You are welcome! I am happy to hear that this tutorial was useful to you.

      Reply
      • Yes it was very useful, although something that wasn’t clear from the tutorial is that the system only works if your form is rendered by the same controller as the controller that processes the form submission. Worth knowing if @SessionAttributes is giving you trouble! I had my form rendered in ControllerA, but the form was being processed in ControllerB, so it didn’t work.

        Reply
        • What was the problem in this scenario? The controllerA did not find the form object after it was processed by ControllerB?

          I have not personally implemented a form which uses two different controllers but I assume that if you add the @SessionAttributes annotation to both controllers, it might work. Did you try this out?

          Maybe I should could write a separate blog post from this scenario. Did you have some reason to implement the form in this way?

          Reply
  • Hi,

    Thank you for your blogposts about MockMvc testing. They have been already very useful to me!

    I have a small problem, which I can’t fix. I googled around a bit but no avail.

    On my DTO I have an Object whose properties I wish to test. I thought I could use the following:

    .andExpect(model().attribute(“myDto”, hasProperty(“someObject.name”, is(“test”))));

    But I get the following error:

    java.lang.AssertionError: Model attribute ‘myDto’
    Expected: hasProperty(“someObject.name”, is “test”)
    but: No property “someObject.name”

    Do you have a solution or workaround for this? Or am I doing something else wrong here?

    Many thanks,
    Frederik

    Reply
    • Hi,

      You are welcome. I am happy to hear that these blog posts have been useful to you!

      About your problem:

      I think that the hasProperty() matcher does not support nested properties. However, since it accepts a Hamcrest matcher as a second method parameter, it should be possible to chain Hamcrest matchers like this:

      .andExpect(model().attribute(“myDto”, hasProperty(“someObject”, hasProperty(“name”, is(“test”))));

      I have not tested this but it should do the trick. Let me know if it solved your problem.

      Reply
      • Yes, it works like that! Thank you very much!

        Reply
        • You are welcome!

          Reply
  • Hi Wile testing with Mock mvc. I have a problem autowiring bean in custom validators. My custom validator has a autowired dao property which is not loading i am getting null pointer. any thoughts on this?

    Reply
    • I have few questions for you:

      Is the NoSuchBeanDefinitionException thrown or do you just run into NPE in your test? The reason why I ask this is that if the dao bean is not found from the application context of your test, the NoSuchBeanDefinitionException should be thrown. Are you sure that the dao is null?

      I assume that the dao property is injected when you run your application. Is this correct?

      Reply
  • NPE at customvalidator @autowired property.

    Actually i wrote a custom validator which has @autowired Mydao mydao;
    mydao used to checks string exits already in the database.

    Instead of @Valid in the controller method i have @MyappCheck I am getting NPE while running test case control is going to my customvalidator class but @autowired property showing null when i debug test.

    Reply
  • I tried but It did not worked. Thank you.

    Reply
  • Your tutorials have been a godsend. Seems like no one who figures out how to use spring mvc bothers to write a comprehensive guide. Thank you so much. :)

    Reply
    • Thank you for your kind words. I am happy to hear that these tutorials were useful to you.

      Reply
  • First, thank you for all your posts. They are very informative, their presentation is impeccable and you clearly have well though-out ideas.

    I’m reaching out because I’m struggling with some basic questions. Maybe I’m over-thinking things, but here it goes.

    We all agree that web applications should have a clean RESTful API. This means addressing resources using proper URIs as much as using appropriate HTTP verbs to describe actions on these resources. While this can be somewhat straight-forward when supporting programmatic clients, the requirement to support HTML browsers complicates things a bit. Take for example your “create Todo” example. What I gather from pure REST principles is that the API should handle a POST on /todos and then return a 201 Created status with a Location: /todos/{id} header. However, when accepting a similar request from a web browser, a human user would expect to be redirected to the Todo list page, and so the API should return a 302 with Location: /todos.

    What’s a clean API to do in these conditions? Returning different status codes based on the content type doesn’t seem to be a good idea. An option would be to return 201 with both a Location header and a Refresh header, but Refresh doesn’t seem to be part of the HTTP standard (although supported by every major browser) and AFAIK this practice isn’t common place.

    What’s your take on this?
    Thanks a bunch!

    Reply
    • First, thank you for your kind words. I really appreciate them.

      I guess there are two situations which we must take into consideration:

      1. The backend of our application has only a REST API which is used by programmatic clients (one of these clients is a single page web application). In this scenario it seems pretty clear that the HTTP status code 201 should be returned when a new todo entry is created.
      2. We have a “normal” Spring MVC application which offers a REST API for programmatic clients. This is the trickier scenario, and I would probaby solve this problem by returning the HTTP status code 201 when a new todo entry is created by using the REST API. I agree that returning different status codes is confusing but I suspect that the user of our “normal” web application doesn’t really care about the returned HTTP status code(he might not even know what a HTTP status code is). He just wants to be sure that a new todo entry was created. That is why I think that returning different HTTP status is acceptable in this case.

      I hope that this answer helps you to clarify your thoughts.

      Reply

Leave a Comment