本教程的第一部分描述了如何配置使用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. 创建调用我们的service方法时返回的测试数据。 在为测试创建测试数据时,我们使用称为测试数据生成器的概念。
  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;@Autowiredprivate TodoService todoServiceMock;//Add WebApplicationContext field here//The setUp() method is omitted.@Testpublic 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;@Autowiredprivate TodoService todoServiceMock;//Add WebApplicationContext field here//The setUp() method is omitted.@Testpublic 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;@Autowiredprivate TodoService todoServiceMock;//Add WebApplicationContext field here//The setUp() method is omitted.@Testpublic 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);}
}

处理“添加待办事项”输入表单的表单提交

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

预期行为

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

  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;@Autowiredprivate TodoService todoServiceMock;//Add WebApplicationContext field here//The setUp() method is omitted.@Testpublic 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条目已添加到数据库

其次,我们必须编写一个测试,以确保在将新的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;@Autowiredprivate TodoService todoServiceMock;//Add WebApplicationContext field here//The setUp() method is omitted.@Testpublic 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

Spring MVC控制器的单元测试:“普通”控制器相关推荐

  1. 吐血整理!14个编写Spring MVC控制器的实用小技巧

    全文共4248字,预计学习时长9分钟 编写Spring MVC控制器的最佳技巧 本文介绍了编写Spring MVC框架的控制器(controller)的基础技巧和最佳操作.在Spring MVC框架中 ...

  2. Spring MVC测试框架入门–第2部分

    这个迷你系列的第一个博客介绍了Spring MVC测试框架,并展示了其在单元测试Spring MVC Controller类中作为控制器而不是POJO进行单元测试的用途. 现在是时候讨论使用框架进行集 ...

  3. Spring MVC 的xml一些配置

    1.可以自动加载注解驱动,通过注解找到对应Controller <!-- spring MVC 注解驱动 --><mvc:annotation-driven></mvc: ...

  4. Spring MVC 入门示例讲解

    在本例中,我们将使用Spring MVC框架构建一个入门级web应用程序.Spring MVC 是Spring框架最重要的的模块之一.它以强大的Spring IoC容器为基础,并充分利用容器的特性来简 ...

  5. Spring MVC 完整示例

    在本例中,我们将使用Spring MVC框架构建一个入门级web应用程序.Spring MVC 是Spring框架最重要的的模块之一.它以强大的Spring IoC容器为基础,并充分利用容器的特性来简 ...

  6. Spring MVC的DispatcherServlet – Java开发人员应该知道的10件事

    如果您使用过Spring MVC,那么您应该知道什么是DispatcherServlet? 它实际上是Spring MVC的心脏,确切地说是MVC设计模式或控制器的C语言. 应该由Spring MVC ...

  7. 在Spring MVC中,InternalResourceViewResolver做什么?

    InternalResourceViewResolver是Spring MVC框架中ViewResolver一个实现,它将逻辑视图名称(例如" hello")解析为内部物理资源(例 ...

  8. spring mvc 教程_Spring MVC开发–快速教程

    spring mvc 教程 这是我们的JCG合作伙伴之一,来自Manoj的有关使用Spring开发Web应用程序的简短教程, 网址为" The Khangaonkar Report &quo ...

  9. Spring MVC –揭秘了@RequestBody和@ResponseBody

    在这篇文章中,我想对Spring MVC进行一些深入的探讨,以揭示将请求转换为参数对象后在幕后发生的情况,反之亦然. 在开始之前,我想解释这些注释的目的. @RequestBody和@Response ...

最新文章

  1. LeetCode 所有题目总结
  2. 数据仓库dw层_数据仓库分层之辩
  3. asyncio.Protocol socket 断线重连
  4. 影院平台搭建 - (6)一个靠谱的视频播放方案的感想
  5. 选址问题java_学习使用分治算法来解决邮局选址问题(Java实现)
  6. 运筹学与最优化方法_[公开课]运筹学之线性规划算法十二讲
  7. python 倒排索引(Inverted Index)
  8. Android 8.0 学习(3)---Android Treble
  9. C++ OI图论 学习笔记(初步完结)
  10. MyBatis动态SQL的List传值错误
  11. leetcode刷题日记-三个无重叠子数组的最大和
  12. JS日期前后一天方法
  13. 01-nodeJs下载及安装
  14. Win10自带的录屏功能怎么使用?
  15. 思考题4:掷骰子游戏
  16. python求平均值_如何用python求平均值
  17. 【AE表达式】300多个人名正从宇宙中飞来……
  18. iphone描述文件
  19. WinRAR突现骇人漏洞,官方:没必要修复
  20. 论文笔记:联邦学习——Federated Learning: Strategies for Improving Communication Efficiency

热门文章

  1. id和class到底要用哪一个?
  2. hdu 1053 Entropy (哈夫曼树)
  3. 疯狂java讲义之流程控制与数组
  4. java中System类简介(转)
  5. asp.net 验证正则表达式
  6. QT QSqlTabModel 学习,用于从数据库中存取修改等操作。
  7. Linux下tomcat安装及优化
  8. 自己对多线程的一点思考
  9. python3 配置文件操作库 configparser 读取配置文件后 元组列表转字典
  10. linux ssh连接 出现 Host key verification failed 错误 解决方法