Spring MVC控制器的单元测试:“普通”控制器

本教程的第一部分描述了如何配置使用Spring MVC Test框架的单元测试 。 现在是时候动手做,学习如何为“常规”控制器编写单元测试了。

显而易见的下一个问题是:

什么是普通控制器?

好吧,一个普通的控制器(在此博客文章的上下文中)是一个控制器,它要么呈现视图,要么处理表单提交。

让我们开始吧。

注意 :建议您在阅读此博客文章之前先阅读本教程第一部分 (如果已经阅读过,可以继续阅读)

使用Maven获取所需的依赖关系

通过将以下依赖项声明添加到示例应用程序的POM文件中,我们可以获得所需的测试依赖项:

  • 杰克逊2.2.1(核心和数据绑定模块)。 我们使用Jackson将对象转换为url编码的String对象。
  • Hamcrest 1.3。 在为响应编写断言时,我们使用Hamcrest匹配器。
  • JUnit 4.11(不包括hamcrest-core依赖性)。
  • Mockito 1.9.5
  • Spring测试3.2.3发布

pom.xml文件的相关部分如下所示:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.2.1</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.2.1</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.hamcrest</groupId>
    <artifactId>hamcrest-all</artifactId>
    <version>1.3</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.11</version>
    <scope>test</scope>
    <exclusions>
        <exclusion>
            <artifactId>hamcrest-core</artifactId>
            <groupId>org.hamcrest</groupId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>1.9.5</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>3.2.3.RELEASE</version>
    <scope>test</scope>
</dependency>

让我们继续研究如何使用Spring MVC Test框架为Spring MVC控制器编写单元测试。

编写控制器方法的单元测试

我们编写的用于测试控制器方法行为的每个单元测试均包含以下步骤:

  1. 我们向已测试的控制器方法发送请求。
  2. 我们确认我们收到了预期的答复。

Spring MVC测试框架具有一些“核心”类,我们可以使用这些类在测试中实现这些步骤。 这些类的描述如下:

  • 我们可以使用MockMvcRequestBuilders类的静态方法来构建请求。 更具体地说,我们可以创建请求构建器 ,然后将其作为方法参数传递给执行实际请求的方法。
  • MockMvc类是测试的主要入口点。 我们可以通过调用其perform(RequestBuilder requestBuilder)方法来执行请求。
  • 我们可以使用MockMvcResultMathers类的静态方法为收到的响应编写断言。

接下来,我们将看一些示例,这些示例演示了如何在单元测试中使用这些类。 我们将为以下控制器方法编写单元测试:

  • 第一个控制器方法呈现一个页面,该页面显示待办事项列表。
  • 第二种控制器方法呈现一个页面,该页面显示单个待办事项的信息。
  • 第三种控制器方法处理表单的表单提交,该表单用于向数据库添加新的待办事项。
渲染Todo条目列表页面

让我们首先看一下用于呈现待办事项条目列表页面的controller方法的实现。

预期行为

用于显示所有待办事项信息的控制器方法的实现包括以下步骤:

  1. 它通过调用TodoService接口的findAll()方法来获取待办事项。 此方法返回Todo对象的列表。
  2. 它将接收到的列表添加到模型。
  3. 它返回渲染视图的名称。

TodoController类的相关部分如下所示:

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import java.util.List;

@Controller
public class TodoController {

    private final TodoService service;
   
    @RequestMapping(value = "/", method = RequestMethod.GET)
    public String findAll(Model model) {
        List<Todo> models = service.findAll();
        model.addAttribute("todos", models);
        return "todo/list";
    }
}

现在,我们准备为此方法编写单元测试。 让我们看看我们如何做到这一点。

测试:找到待办事项

我们可以通过以下步骤为此控制器方法编写单元测试:

  1. 创建在调用我们的服务方法时返回的测试数据。 在为测试创建测试数据时,我们使用称为测试数据生成器的概念。
  2. 配置使用的模拟对象以在调用其findAll()方法时返回创建的测试数据。
  3. 执行GET请求以发送URL'/'。
  4. 确保返回HTTP状态代码200。
  5. 确保返回的视图的名称为“ todo / list”。
  6. 确保请求转发到URL'/WEB-INF/jsp/todo/list.jsp'。
  7. 确保名为todos的模型属性包含两个项目。
  8. 确保名为todos的模型属性包含正确的项目。
  9. 验证模拟对象的findAll()方法仅被调用一次。
  10. 确保在测试过程中未调用模拟对象的其他方法。

我们的单元测试的源代码如下所示:

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import java.util.Arrays;

import static org.hamcrest.Matchers.*;
import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {TestContext.class, WebAppContext.class})
@WebAppConfiguration
public class TodoControllerTest {

    private MockMvc mockMvc;

    @Autowired
    private TodoService todoServiceMock;

    //Add WebApplicationContext field here

    //The setUp() method is omitted.

    @Test
    public void findAll_ShouldAddTodoEntriesToModelAndRenderTodoListView() throws Exception {
        Todo first = new TodoBuilder()
                .id(1L)
                .description("Lorem ipsum")
                .title("Foo")
                .build();

        Todo second = new TodoBuilder()
                .id(2L)
                .description("Lorem ipsum")
                .title("Bar")
                .build();

        when(todoServiceMock.findAll()).thenReturn(Arrays.asList(first, second));

        mockMvc.perform(get("/"))
                .andExpect(status().isOk())
                .andExpect(view().name("todo/list"))
                .andExpect(forwardedUrl("/WEB-INF/jsp/todo/list.jsp"))
                .andExpect(model().attribute("todos", hasSize(2)))
                .andExpect(model().attribute("todos", hasItem(
                        allOf(
                                hasProperty("id", is(1L)),
                                hasProperty("description", is("Lorem ipsum")),
                                hasProperty("title", is("Foo"))
                        )
                )))
                .andExpect(model().attribute("todos", hasItem(
                        allOf(
                                hasProperty("id", is(2L)),
                                hasProperty("description", is("Lorem ipsum")),
                                hasProperty("title", is("Bar"))
                        )
                )));

        verify(todoServiceMock, times(1)).findAll();
        verifyNoMoreInteractions(todoServiceMock);
    }
}
渲染View Todo输入页面

在为控制器方法编写实际的单元测试之前,我们必须仔细研究该方法的实现。 让我们继续前进,了解控制器的实现方式。

预期行为

通过执行以下步骤来实现用于显示单个待办事项信息的控制器方法:

  1. 它通过调用TodoService接口的findById()方法获取请求的待办事项条目,并将请求的待办事项条目的ID作为方法参数传递。 此方法返回找到的待办事项条目。 如果未找到待办事项条目,则此方法抛出TodoNotEntryNotFoundException
  2. 它将找到的待办事项条目添加到模型中。
  3. 它返回渲染视图的名称。

我们的控制器方法的源代码如下所示:

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

@Controller
public class TodoController {

    private final TodoService service;

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

我们的下一个问题是:

抛出TodoEntryNotFoundException会发生什么?

在本教程的上一部分中,我们创建了一个异常解析器bean,用于处理控制器类抛出的异常。 该bean的配置如下所示:

@Bean
public SimpleMappingExceptionResolver exceptionResolver() {
    SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();

    Properties exceptionMappings = new Properties();

    exceptionMappings.put("net.petrikainulainen.spring.testmvc.todo.exception.TodoNotFoundException", "error/404");
    exceptionMappings.put("java.lang.Exception", "error/error");
    exceptionMappings.put("java.lang.RuntimeException", "error/error");

    exceptionResolver.setExceptionMappings(exceptionMappings);

    Properties statusCodes = new Properties();

    statusCodes.put("error/404", "404");
    statusCodes.put("error/error", "500");

    exceptionResolver.setStatusCodes(statusCodes);

    return exceptionResolver;
}

如我们所见,如果抛出TodoEntryNotFoundException ,我们的应用程序将呈现“错误/ 404”视图并返回HTTP状态代码404。

显然,我们必须为此控制器方法编写两个测试:

  1. 我们必须编写一个测试,以确保在未找到todo条目时,我们的应用程序能够正常运行。
  2. 找到待办事项后,我们必须编写一个测试来验证我们的应用程序是否正常运行。

让我们看看如何编写这些测试。

测试1:找不到待办事项条目

首先,当找不到请求的待办事项条目时,我们必须确保我们的应用程序处于工作状态。 我们可以按照以下步骤编写测试来确保这一点:

  1. 将模拟对象配置为在调用findById()方法且请求的待办事项条目的ID为1L时引发TodoNotFoundException
  2. 执行GET请求以发送url'/ todo / 1'。
  3. 验证是否返回了HTTP状态代码404。
  4. 确保返回的视图的名称为“ error / 404”。
  5. 确保将请求转发到URL“ /WEB-INF/jsp/error/404.jsp”。
  6. 验证仅使用正确的方法参数(1L)调用一次TodoService接口的findById()方法。
  7. 验证在此测试期间没有调用模拟对象的其他方法。

我们的单元测试的源代码如下所示:

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {TestContext.class, WebAppContext.class})
@WebAppConfiguration
public class TodoControllerTest {

    private MockMvc mockMvc;

    @Autowired
    private TodoService todoServiceMock;

    //Add WebApplicationContext field here

    //The setUp() method is omitted.

    @Test
    public void findById_TodoEntryNotFound_ShouldRender404View() throws Exception {
        when(todoServiceMock.findById(1L)).thenThrow(new TodoNotFoundException(""));

        mockMvc.perform(get("/todo/{id}", 1L))
                .andExpect(status().isNotFound())
                .andExpect(view().name("error/404"))
                .andExpect(forwardedUrl("/WEB-INF/jsp/error/404.jsp"));

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

测试2:找到Todo条目

其次,我们必须编写一个测试,以确保找到待办事项时控制器能够正常工作。 我们可以按照以下步骤进行操作:

  1. 创建Todo对象,该对象在调用我们的service方法时返回。 同样,我们使用测试数据生成器创建返回的Todo对象。
  2. 配置我们的模拟对象以在使用方法参数1L调用其findById()方法时返回创建的Todo对象。
  3. 执行GET请求以发送url'/ todo / 1'。
  4. 验证是否返回HTTP状态代码200。
  5. 确保返回的视图名称为“ todo / view”。
  6. 确保将请求转发到URL“ /WEB-INF/jsp/todo/view.jsp”。
  7. 验证名为todo的模型对象的ID为1L。
  8. 验证名为todo的模型对象的描述为“ Lorem ipsum”。
  9. 验证名为todo的模型对象的标题为“ Foo”。
  10. 确保使用正确的方法参数(1L)仅对模拟对象的findById()方法进行一次调用。
  11. 确保在测试期间未调用模拟对象的其他方法。

我们的单元测试的源代码如下所示:

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import static org.hamcrest.Matchers.hasProperty;
import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {TestContext.class, WebAppContext.class})
@WebAppConfiguration
public class TodoControllerTest {

    private MockMvc mockMvc;

    @Autowired
    private TodoService todoServiceMock;

    //Add WebApplicationContext field here

    //The setUp() method is omitted.

    @Test
    public void findById_TodoEntryFound_ShouldAddTodoEntryToModelAndRenderViewTodoEntryView() throws Exception {
        Todo found = new TodoBuilder()
                .id(1L)
                .description("Lorem ipsum")
                .title("Foo")
                .build();

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

        mockMvc.perform(get("/todo/{id}", 1L))
                .andExpect(status().isOk())
                .andExpect(view().name("todo/view"))
                .andExpect(forwardedUrl("/WEB-INF/jsp/todo/view.jsp"))
                .andExpect(model().attribute("todo", hasProperty("id", is(1L))))
                .andExpect(model().attribute("todo", hasProperty("description", is("Lorem ipsum"))))
                .andExpect(model().attribute("todo", hasProperty("title", is("Foo"))));

        verify(todoServiceMock, times(1)).findById(1L);
        verifyNoMoreInteractions(todoServiceMock);
    }
}
处理“添加待办事项”输入表单的表单提交

同样,我们将首先查看控制器方法的预期行为,然后再为其编写单元测试。

预期行为

通过执行以下步骤来实现处理添加待办事项条目表单的表单提交的controller方法:

  1. 它检查作为方法参数给出的BindingResult对象是否没有任何错误。 如果发现错误,它将返回表单视图的名称。
  2. 它通过调用TodoService接口的add()方法添加一个新的Todo条目,并将form对象作为方法参数传递。 此方法创建一个新的待办事项条目并返回它。
  3. 它创建有关添加的待办事项条目的反馈消息,并将该消息添加到作为方法参数给出的RedirectAttributes对象。
  4. 它将添加的待办事项条目的ID添加到RedirectAttributes对象。
  5. 它返回重定向视图的名称,该名称将请求重定向到视图待办事项输入页面。

TodoController类的相关部分如下所示:

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

import javax.validation.Valid;
import java.util.Locale;

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

    private final TodoService service;

    private final MessageSource messageSource;

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

        Todo added = service.add(dto);

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

        return createRedirectViewPath("todo/view");
    }

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

如我们所见,控制器方法使用TodoDTO对象作为表单对象。 TodoDTO类是一个简单的DTO类,其源代码如下所示:

import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.NotEmpty;

public class TodoDTO {

    private Long id;

    @Length(max = 500)
    private String description;

    @NotEmpty
    @Length(max = 100)
    private String title;

    //Constructor and other methods are omitted.
}

TodoDTO类声明一些验证约束,这些约束如下所述:

  • 待办事项的标题不能为空。
  • 说明的最大长度为500个字符。
  • 标题的最大长度为100个字符。

如果考虑为该控制器方法编写的测试,则很明显,我们必须确保

  1. 验证失败时,控制器方法为工作属性。
  2. 当待办事项条目添加到数据库时,控制器方法是工作属性。

让我们找出如何编写这些测试。

测试1:验证失败

首先,我们必须编写一个测试,以确保验证失败时我们的控制器方法能够正常工作。 我们可以按照以下步骤编写此测试:

  1. 创建一个具有101个字符的标题
  2. 创建一个包含501个字符的描述
  3. 使用我们的测试数据构建器创建一个新的TodoDTO对象。 设置对象的标题描述
  4. 执行POST请求以发送网址“ todo / add”。 将请求的内容类型设置为“ application / x-www-form-urlencoded”。 确保我们的表单对象的内容在请求的正文中发送。 将表单对象设置为会话。
  5. 验证是否返回HTTP状态代码200。
  6. 确认返回的视图名称为“ todo / add”。
  7. 验证请求是否转发到URL'/WEB-INF/jsp/todo/add.jsp'。
  8. 验证我们的模型属性在标题描述字段中是否存在字段错误。
  9. 确保我们的model属性的id为null。
  10. 确保对我们的模型属性的描述正确。
  11. 确保模型属性的标题正确。
  12. 确保在测试过程中未调用模拟对象的方法。

我们的单元测试的源代码如下所示:

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import static org.hamcrest.Matchers.hasProperty;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {TestContext.class, WebAppContext.class})
@WebAppConfiguration
public class TodoControllerTest {

    private MockMvc mockMvc;

    @Autowired
    private TodoService todoServiceMock;

    //Add WebApplicationContext field here

    //The setUp() method is omitted.

    @Test
    public void add_DescriptionAndTitleAreTooLong_ShouldRenderFormViewAndReturnValidationErrorsForTitleAndDescription() throws Exception {
        String title = TestUtil.createStringWithLength(101);
        String description = TestUtil.createStringWithLength(501);

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

        mockMvc.perform(post("/todo/add")
                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                .content(TestUtil.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))));

        verifyZeroInteractions(todoServiceMock);
    }
}

我们的测试用例调用了TestUtil类的一些静态方法。 下面介绍了这些方法:

  • createStringWithLength(int length)方法使用给定的长度创建一个新的String对象,并返回创建的对象。
  • 所述convertObjectToFormUrlEncodedBytes(Object对象)方法转换对象变成形式URL编码字符串对象,并返回字符串对象的内容作为一个字节数组。

TestUtil类的源代码如下所示:

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public class TestUtil {

    public static byte[] convertObjectToFormUrlEncodedBytes(Object object) {
        ObjectMapper mapper = new ObjectMapper();
        mapper.setSerializationInclusion(JsonInclude.Include.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();
    }

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

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

        return builder.toString();
    }
}

测试2:Todo条目已添加到数据库

其次,我们必须编写一个测试,以确保在将新的待办事项添加到数据库时,控制器能够正常工作。 我们可以按照以下步骤编写此测试:

  1. 通过使用测试数据构建器类创建一个表单对象。 将“ legal”值设置为所创建对象的标题描述字段。
  2. 创建一个Todo对象,该对象在调用TodoService接口的add()方法时返回。
  3. 配置我们的模拟对象,使其在调用其add()方法并将创建的表单对象作为方法参数给出时返回创建的Todo对象。
  4. 执行POST请求以发送网址“ todo / add”。 将请求的内容类型设置为“ application / x-www-form-urlencoded”。 确保我们的表单对象的内容在请求的正文中发送。 将表单对象设置为会话。
  5. 验证是否返回了HTTP状态代码302。
  6. 确保返回的视图的名称为'redirect:todo / {id}'。
  7. 确保将请求重定向到URL'/ todo / 1'。
  8. 验证名为id的模型属性为“ 1”。
  9. 验证是否设置了反馈消息。
  10. 验证我们的模拟对象的add()方法仅被调用一次,并且表单对象作为方法参数给出。
  11. 验证在测试过程中没有调用过模拟对象的其他方法。

我们的单元测试的源代码如下所示:

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {TestContext.class, WebAppContext.class})
@WebAppConfiguration
public class TodoControllerTest {

    private MockMvc mockMvc;

    @Autowired
    private TodoService todoServiceMock;

    //Add WebApplicationContext field here

    //The setUp() method is omitted.

    @Test
    public void add_NewTodoEntry_ShouldAddTodoEntryAndRenderViewTodoEntryView() throws Exception {
        TodoDTO formObject = new TodoDTOBuilder()
                .description("description")
                .title("title")
                .build();

        Todo added = new TodoBuilder()
                .id(1L)
                .description(formObject.getDescription())
                .title(formObject.getTitle())
                .build();

        when(todoServiceMock.add(formObject)).thenReturn(added);

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

        verify(todoServiceMock, times(1)).add(formObject);
        verifyNoMoreInteractions(todoServiceMock);
    }
}

摘要

现在,我们使用Spring MVC Test框架为“常规”控制器方法编写了一些单元测试。 本教程讲授了四件事:

  • 我们学会了创建请求,这些请求由经过测试的控制器方法处理。
  • 我们学会了为已测试的控制器方法返回的响应编写断言。
  • 我们学习了如何为呈现视图的控制器方法编写单元测试。
  • 我们学会了为处理表单提交的控制器方法编写单元测试。

本教程的下一部分描述了如何为REST API编写单元测试。

PS此博客文章的示例应用程序可在Github上获得 。 我建议您检查一下,因为它有一些单元测试,而本博客文章中未涉及。

参考: Spring MVC控制器的单元测试: Petri Kainulainen博客上来自我们JCG合作伙伴 Petri Kainulainen的“常规”控制器

翻译自: https://www.javacodegeeks.com/2013/07/unit-testing-of-spring-mvc-controllers-normal-controllers.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值