spring

Spring MVC提供了一种创建REST API的简便方法。 但是,为这些API编写全面而快速的单元测试一直很麻烦。 Spring MVC测试框架的发布使我们可以编写可读,全面且快速的单元测试。

这篇博客文章描述了如何使用Spring MVC Test框架编写REST API的单元测试。 在这篇博客中,我们将为控制器方法编写单元测试,这些方法为待办事项提供CRUD功能。

让我们开始吧。

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

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

  • Hamcrest 1.3( hamcrest-all )。 在为响应编写断言时,我们使用Hamcrest匹配器。
  • Junit 4.11。 我们需要排除hamcrest-core依赖性,因为我们已经添加了hamcrest-all依赖性。
  • Mockito 1.9.5( mockito-core )。 我们使用Mockito作为我们的模拟库。
  • Spring测试3.2.3发布
  • JsonPath 0.8.1( json-pathjson-path-assert )。 在为REST API返回的JSON文档编写断言时,我们使用JsonPath。

相关的依赖项声明如下所示:

<dependency><groupId>org.hamcrest</groupId><artifactId>hamcrest-all</artifactId><version>1.3</version><scope>test</scope>
</dependency>
<dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.11</version><scope>test</scope><exclusions><exclusion><artifactId>hamcrest-core</artifactId><groupId>org.hamcrest</groupId></exclusion></exclusions>
</dependency>
<dependency><groupId>org.mockito</groupId><artifactId>mockito-core</artifactId><version>1.9.5</version><scope>test</scope>
</dependency>
<dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>3.2.3.RELEASE</version><scope>test</scope>
</dependency>
<dependency><groupId>com.jayway.jsonpath</groupId><artifactId>json-path</artifactId><version>0.8.1</version><scope>test</scope>
</dependency>
<dependency><groupId>com.jayway.jsonpath</groupId><artifactId>json-path-assert</artifactId><version>0.8.1</version><scope>test</scope>
</dependency>

让我们继续讨论一下单元测试的配置。

配置我们的单元测试

我们将在此博客文章中编写的单元测试使用基于Web应用程序上下文的配置。 这意味着我们通过使用应用程序上下文配置类或XML配置文件来配置Spring MVC基础结构。

因为本教程的第一部分描述了配置应用程序的应用程序上下文时应遵循的原则,所以本博文中未讨论此问题。

但是,我们必须在这里解决一件事。

配置示例应用程序的Web层的应用程序上下文配置类(或文件)不会创建异常解析器bean。 本教程前面部分中使用的SimpleMappingExceptionResolver类将异常类名称映射到引发配置的异常时呈现的视图。

如果我们正在实现“常规” Spring MVC应用程序,那么这是有意义的。 但是,如果要实现REST API,则希望将异常转换为HTTP状态代码。 默认情况下,由ResponseStatusExceptionResolver类提供此行为。

我们的示例应用程序还具有一个自定义异常处理程序类,该类以@ControllerAdvice批注进行批注。 此类处理验证错误和应用程序特定的异常。 我们将在本博客文章的后面部分详细讨论此类。

让我们继续前进,了解如何为REST API编写单元测试。

编写REST API的单元测试

在开始为REST API编写单元测试之前,我们需要了解两点:

  • 我们需要知道Spring MVC Test框架的核心组件是什么。 这些组件在本教程的第二部分中进行了描述。
  • 我们需要知道如何使用JsonPath表达式编写JSON文档的断言。 我们可以通过阅读我的博客文章获得此信息,该文章描述了如何使用JsonPath编写干净的断言。

接下来,我们将看到运行中的Spring MVC测试框架,并为以下控制器方法编写单元测试:

  • 第一个控制器方法返回待办事项列表。
  • 第二种控制器方法返回单个待办事项的信息。
  • 第三种控制器方法将新的待办事项条目添加到数据库,并返回添加的待办事项条目。

获取待办事项

第一种控制器方法返回从数据库中找到的待办事项列表。 让我们首先看一下该方法的实现。

预期行为

通过执行以下步骤来实现将所有待办事项返回到数据库的控制器方法:

  1. 它处理发送到url'/ api / todo'的GET请求。
  2. 它通过调用TodoService接口的findAll()方法来获取Todo对象的列表。 此方法返回存储在数据库中的所有待办事项。 这些待办事项条目总是以相同的顺序返回。
  3. 它将接收到的列表转换为TodoDTO对象的列表。
  4. 它返回包含TodoDTO对象的列表。

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

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;import java.util.ArrayList;
import java.util.List;@Controller
public class TodoController {private TodoService service;@RequestMapping(value = "/api/todo", method = RequestMethod.GET)@ResponseBodypublic List<TodoDTO> findAll() {List<Todo> models = service.findAll();return createDTOs(models);}private List<TodoDTO> createDTOs(List<Todo> models) {List<TodoDTO> dtos = new ArrayList<>();for (Todo model: models) {dtos.add(createDTO(model));}return dtos;}private TodoDTO createDTO(Todo model) {TodoDTO dto = new TodoDTO();dto.setId(model.getId());dto.setDescription(model.getDescription());dto.setTitle(model.getTitle());return dto;}
}

当返回TodoDTO对象列表时,Spring MVC将该列表转换为包含对象集合的JSON文档。 返回的JSON文档如下所示:

[{"id":1,"description":"Lorem ipsum","title":"Foo"},{"id":2,"description":"Lorem ipsum","title":"Bar"}
]

让我们继续并编写一个单元测试,以确保此控制器方法按预期工作。

测试:找到待办事项条目

我们可以按照以下步骤为该控制器方法编写单元测试:

  1. 创建测试数据,该数据在调用TodoService接口的findAll()方法时返回。 我们通过使用测试数据构建器类来创建测试数据。
  2. 配置我们的模拟对象,使其在调用findAll()方法时返回创建的测试数据。
  3. 执行GET请求以获取网址“ / api / todo”。
  4. 验证是否返回了HTTP状态代码200。
  5. 确认响应的内容类型为“ application / json”,其字符集为“ UTF-8”。
  6. 使用JsonPath表达式$获取待办事项的集合,并确保返回两个待办事项。
  7. 通过使用JsonPath表达式$ [0] .id$ [0] .description$ [0] .title来获取第一个待办事项的ID描述标题。 验证是否返回了正确的值。
  8. 通过使用JsonPath表达式$ [1] .id$ [1] .description$ [1] .title来获取第二个待办事项的ID描述和标题。 验证是否返回了正确的值。
  9. 验证TodoService接口的findAll()方法仅被调用一次。
  10. 确保在测试过程中没有调用模拟对象的其他方法。

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

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;import java.util.Arrays;import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {TestContext.class, WebAppContext.class})
@WebAppConfiguration
public class TodoControllerTest {private MockMvc mockMvc;@Autowiredprivate TodoService todoServiceMock;//Add WebApplicationContext field here.//The setUp() method is omitted.@Testpublic void findAll_TodosFound_ShouldReturnFoundTodoEntries() throws Exception {Todo first = new TodoBuilder().id(1L).description("Lorem ipsum").title("Foo").build();Todo second = new TodoBuilder().id(2L).description("Lorem ipsum").title("Bar").build();when(todoServiceMock.findAll()).thenReturn(Arrays.asList(first, second));mockMvc.perform(get("/api/todo")).andExpect(status().isOk()).andExpect(content().contentType(TestUtil.APPLICATION_JSON_UTF8)).andExpect(jsonPath("$", hasSize(2))).andExpect(jsonPath("$[0].id", is(1))).andExpect(jsonPath("$[0].description", is("Lorem ipsum"))).andExpect(jsonPath("$[0].title", is("Foo"))).andExpect(jsonPath("$[1].id", is(2))).andExpect(jsonPath("$[1].description", is("Lorem ipsum"))).andExpect(jsonPath("$[1].title", is("Bar")));verify(todoServiceMock, times(1)).findAll();verifyNoMoreInteractions(todoServiceMock);}
}

我们的单元测试使用一个称为APPLICATION_JSON_UTF8的常量,该常量在TestUtil类中声明。 该常量的值是MediaType对象,其内容类型为“ application / json”,字符集为“ UTF-8”。

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

public class TestUtil {public static final MediaType APPLICATION_JSON_UTF8 = new MediaType(MediaType.APPLICATION_JSON.getType(),
MediaType.APPLICATION_JSON.getSubtype(),                       Charset.forName("utf8")                    );
}

获取待办事项条目

我们必须测试的第二个控制器方法返回单个待办事项的信息。 让我们找出如何实现此控制器方法。

预期行为

通过执行以下步骤来实现返回单个待办条目信息的控制器方法:

  1. 它处理发送到url'/ api / todo / {id}'的GET请求。 {id}是一个路径变量,其中包含请求的待办事项条目的ID
  2. 它通过调用TodoService接口的findById()方法来获取请求的待办事项条目,并将请求的待办事项条目的ID作为方法参数传递。 此方法返回找到的待办事项条目。 如果未找到待办事项条目,则此方法将抛出TodoNotFoundException
  3. 它将Todo对象转换为TodoDTO对象。
  4. 它返回创建的TodoDTO对象。

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

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;@Controller
public class TodoController {private TodoService service;@RequestMapping(value = "/api/todo/{id}", method = RequestMethod.GET)@ResponseBodypublic TodoDTO findById(@PathVariable("id") Long id) throws TodoNotFoundException {Todo found = service.findById(id);return createDTO(found);}private TodoDTO createDTO(Todo model) {TodoDTO dto = new TodoDTO();dto.setId(model.getId());dto.setDescription(model.getDescription());dto.setTitle(model.getTitle());return dto;}
}

返回给客户端的JSON文档如下所示:

{"id":1,"description":"Lorem ipsum","title":"Foo"
}

我们的下一个问题是:

抛出TodoNotFoundException会发生什么?

我们的示例应用程序具有异常处理程序类,该异常处理程序类处理由控制器类引发的应用程序特定的异常。 此类具有异常处理程序方法,当抛出TodoNotFoundException时将调用该方法。 此方法的实现将新的日志消息写入日志文件,并确保将HTTP状态代码404发送回客户端。

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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;@ControllerAdvice
public class RestErrorHandler {private static final Logger LOGGER = LoggerFactory.getLogger(RestErrorHandler.class);@ExceptionHandler(TodoNotFoundException.class)@ResponseStatus(HttpStatus.NOT_FOUND)public void handleTodoNotFoundException(TodoNotFoundException ex) {LOGGER.debug("handling 404 error on a todo entry");}
}

我们必须为此控制器方法编写两个单元测试:

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

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

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

首先,当找不到待办事项时,我们必须确保我们的应用程序正常运行。 我们可以按照以下步骤编写一个单元测试来确保这一点:

  1. 将我们的模拟对象配置为在调用findById()方法且所请求的待办事项条目的ID为1L时引发TodoNotFoundException
  2. 执行GET请求以获取url'/ api / todo / 1'。
  3. 验证是否返回了HTTP状态代码404。
  4. 确保使用正确的方法参数(1L)仅调用一次TodoService接口的findById()方法。
  5. 验证在此测试期间没有调用TodoService接口的其他方法。

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

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {TestContext.class, WebAppContext.class})
@WebAppConfiguration
public class TodoControllerTest {private MockMvc mockMvc;@Autowiredprivate TodoService todoServiceMock;//Add WebApplicationContext field here.//The setUp() method is omitted.@Testpublic void findById_TodoEntryNotFound_ShouldReturnHttpStatusCode404() throws Exception {when(todoServiceMock.findById(1L)).thenThrow(new TodoNotFoundException(""));mockMvc.perform(get("/api/todo/{id}", 1L)).andExpect(status().isNotFound());verify(todoServiceMock, times(1)).findById(1L);verifyNoMoreInteractions(todoServiceMock);}
}

测试2:找到Todo条目

其次,我们必须编写一个测试,以确保在找到所请求的待办事项条目时返回正确的数据。 我们可以按照以下步骤编写测试来确保这一点:

  1. 创建Todo对象,该对象在调用我们的service方法时返回。 我们使用测试数据生成器创建此对象。
  2. 配置我们的模拟对象以在使用方法参数1L调用其findById()方法时返回创建的Todo对象。
  3. 执行GET请求以获取url'/ api / todo / 1'。
  4. 验证是否返回了HTTP状态代码200。
  5. 确认响应的内容类型为“ application / json”,其字符集为“ UTF-8”。
  6. 通过使用JsonPath表达式$ .id获取待办事项的ID ,并验证ID为1。
  7. 使用JsonPath表达式$ .description获取待办事项的描述,并验证该描述是否为“ Lorem ipsum”。
  8. 使用JsonPath表达式$ .title获取待办事项的标题,并验证标题为“ Foo”。
  9. 确保使用正确的方法参数(1L)仅调用一次TodoService接口的findById()方法。
  10. 验证测试期间未调用模拟对象的其他方法。

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

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {TestContext.class, WebAppContext.class})
@WebAppConfiguration
public class TodoControllerTest {private MockMvc mockMvc;@Autowiredprivate TodoService todoServiceMock;//Add WebApplicationContext field here.//The setUp() method is omitted.@Testpublic void findById_TodoEntryFound_ShouldReturnFoundTodoEntry() throws Exception {Todo found = new TodoBuilder().id(1L).description("Lorem ipsum").title("Foo").build();when(todoServiceMock.findById(1L)).thenReturn(found);mockMvc.perform(get("/api/todo/{id}", 1L)).andExpect(status().isOk()).andExpect(content().contentType(TestUtil.APPLICATION_JSON_UTF8)).andExpect(jsonPath("$.id", is(1))).andExpect(jsonPath("$.description", is("Lorem ipsum"))).andExpect(jsonPath("$.title", is("Foo")));verify(todoServiceMock, times(1)).findById(1L);verifyNoMoreInteractions(todoServiceMock);}
}

添加新的待办事项

第三种控制器方法将新的待办事项条目添加到数据库,并返回添加的待办事项条目的信息。 让我们继续前进,了解它是如何实现的。

预期行为

通过执行以下步骤来实现向数据库添加新的待办事项条目的控制器方法:

  1. 它处理发送到url'/ api / todo'的POST请求。
  2. 它验证作为方法参数给出的TodoDTO对象。 如果验证失败,则抛出MethodArgumentNotValidException
  3. 通过调用TodoService接口的add()方法,它将新的todo条目添加到数据库中,并将TodoDTO对象作为方法参数传递。 此方法将新的待办事项条目添加到数据库,并返回添加的待办事项条目。
  4. 它将创建的Todo对象转换为TodoDTO对象。
  5. 它返回TodoDTO对象。

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

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;import javax.validation.Valid;@Controller
public class TodoController {private TodoService service;@RequestMapping(value = "/api/todo", method = RequestMethod.POST)@ResponseBodypublic TodoDTO add(@Valid @RequestBody TodoDTO dto) {Todo added = service.add(dto);return createDTO(added);}private TodoDTO createDTO(Todo model) {TodoDTO dto = new TodoDTO();dto.setId(model.getId());dto.setDescription(model.getDescription());dto.setTitle(model.getTitle());return dto;}
}

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

如我们所见,该类声明了三个验证约束,如下所示:

  1. 描述的最大长度为500个字符。
  2. 待办事项的标题不能为空。
  3. 标题的最大长度为100个字符。

如果验证失败,我们的错误处理程序组件将确保

  1. HTTP状态代码400返回到客户端。
  2. 验证错误将作为JSON文档返回给客户端。

因为我已经写了一篇博客文章,描述了如何向REST API添加验证,所以本文中没有讨论错误处理程序组件的实现。

但是,如果验证失败,我们需要知道将哪种JSON文档返回给客户端。 该信息在下面给出。

如果TodoDTO对象的标题描述太长,则会将以下JSON文档返回给客户端:

{"fieldErrors":[{"path":"description","message":"The maximum length of the description is 500 characters."},{"path":"title","message":"The maximum length of the title is 100 characters."}]
}

注意:Spring MVC不保证字段错误的顺序。 换句话说,场错误以随机顺序返回。 在为该控制器方法编写单元测试时,必须考虑到这一点。

另一方面,如果验证没有失败,则我们的控制器方法将以下JSON文档返回给客户端:

{"id":1,"description":"description","title":"todo"
}

我们必须为此控制器方法编写两个单元测试:

  1. 我们必须编写一个测试,以确保验证失败时我们的应用程序正常运行。
  2. 我们必须编写一个测试,以确保在将新的待办事项添加到数据库时,我们的应用程序能够正常运行。

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

测试1:验证失败

我们的第一个测试确保当添加的todo条目的验证失败时,我们的应用程序能够正常运行。 我们可以按照以下步骤编写此测试:

  1. 创建一个具有101个字符的标题
  2. 创建一个包含501个字符的描述
  3. 使用我们的测试数据构建器创建一个新的TodoDTO对象。 设置对象的标题描述
  4. 执行POST请求以发送url'/ api / todo'。 将请求的内容类型设置为“ application / json”。 将请求的字符集设置为“ UTF-8”。 将创建的TodoDTO对象转换为JSON字节并将其发送到请求的正文中。
  5. 验证是否返回了HTTP状态代码400。
  6. 验证响应的内容类型为“ application / json”,并且其内容类型为“ UTF-8”。
  7. 通过使用JsonPath表达式$ .fieldErrors提取字段错误,并确保返回两个字段错误。
  8. 通过使用JsonPath表达式$ .fieldErrors [*]。path获取所有可用路径,并确保找到有关titledescription字段的字段错误。
  9. 通过使用JsonPath表达式$ .fieldErrors [*]。message获取所有可用的错误消息,并确保找到有关标题描述字段的错误消息。
  10. 验证测试期间未调用模拟对象的方法。

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

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.hasSize;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {TestContext.class, WebAppContext.class})
@WebAppConfiguration
public class TodoControllerTest {private MockMvc mockMvc;@Autowiredprivate TodoService todoServiceMock;//Add WebApplicationContext field here.//The setUp() method is omitted.@Testpublic void add_TitleAndDescriptionAreTooLong_ShouldReturnValidationErrorsForTitleAndDescription() throws Exception {String title = TestUtil.createStringWithLength(101);String description = TestUtil.createStringWithLength(501);TodoDTO dto = new TodoDTOBuilder().description(description).title(title).build();mockMvc.perform(post("/api/todo").contentType(TestUtil.APPLICATION_JSON_UTF8).content(TestUtil.convertObjectToJsonBytes(dto))).andExpect(status().isBadRequest()).andExpect(content().contentType(TestUtil.APPLICATION_JSON_UTF8)).andExpect(jsonPath("$.fieldErrors", hasSize(2))).andExpect(jsonPath("$.fieldErrors[*].path", containsInAnyOrder("title", "description"))).andExpect(jsonPath("$.fieldErrors[*].message", containsInAnyOrder("The maximum length of the description is 500 characters.","The maximum length of the title is 100 characters.")));verifyZeroInteractions(todoServiceMock);}
}

我们的单元测试使用TestUtil类的两个静态方法。 下面介绍了这些方法:

  • createStringWithLength(int length)方法使用给定的长度创建一个新的String对象,并返回创建的对象。
  • convertObjectToJsonBytes(Object object)方法将作为方法参数给出的对象转换为JSON文档,并将该文档的内容作为字节数组返回

TestUtil类的源代码如下所示:

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.MediaType;import java.io.IOException;
import java.nio.charset.Charset;public class TestUtil {public static final MediaType APPLICATION_JSON_UTF8 = new MediaType(MediaType.APPLICATION_JSON.getType(), MediaType.APPLICATION_JSON.getSubtype(), Charset.forName("utf8"));public static byte[] convertObjectToJsonBytes(Object object) throws IOException {ObjectMapper mapper = new ObjectMapper();mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);return mapper.writeValueAsBytes(object);}public static String createStringWithLength(int length) {StringBuilder builder = new StringBuilder();for (int index = 0; index < length; index++) {builder.append("a");}return builder.toString();}
}

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

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

  1. 使用我们的测试数据构建器创建一个新的TodoDTO对象。 在标题描述字段中设置“合法”值。
  2. 创建一个Todo对象,该对象在调用TodoService接口的add()方法时返回。
  3. 配置我们的模拟对象,使其在调用其add()方法并将TodoDTO对象作为参数时返回创建的Todo对象。
  4. 执行POST请求以发送url'/ api / todo'。 将请求的内容类型设置为“ application / json”。 将请求的字符集设置为“ UTF-8”。 将创建的TodoDTO对象转换为JSON字节并将其发送到请求的正文中。
  5. 验证是否返回了HTTP状态代码200。
  6. 验证响应的内容类型为“ application / json”,并且其内容类型为“ UTF-8”。
  7. 使用JsonPath表达式$ .id获取返回的待办事项条目的ID ,并验证ID为1。
  8. 使用JsonPath表达式$ .description获取返回的待办事项的描述,并验证该描述是否为“描述”。
  9. 使用JsonPath表达式$ .title获取返回的待办事项的标题,并确保标题为“ title”。
  10. 创建一个ArgumentCaptor对象,该对象可以捕获TodoDTO对象。
  11. 验证TodoService接口的add()方法仅被调用一次,并捕获作为参数给出的对象。
  12. 验证测试期间未调用模拟对象的其他方法。
  13. 验证捕获的TodoDTO对象的ID为null。
  14. 验证所捕获的TodoDTO对象的描述是“说明”。
  15. 验证捕获TodoDTO对象的标题是“冠军”。

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

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;import static junit.framework.Assert.assertNull;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {TestContext.class, WebAppContext.class})
@WebAppConfiguration
public class TodoControllerTest {private MockMvc mockMvc;@Autowiredprivate TodoService todoServiceMock;//Add WebApplicationContext field here.//The setUp() method is omitted.@Testpublic void add_NewTodoEntry_ShouldAddTodoEntryAndReturnAddedEntry() throws Exception {TodoDTO dto = new TodoDTOBuilder().description("description").title("title").build();Todo added = new TodoBuilder().id(1L).description("description").title("title").build();when(todoServiceMock.add(any(TodoDTO.class))).thenReturn(added);mockMvc.perform(post("/api/todo").contentType(TestUtil.APPLICATION_JSON_UTF8).content(TestUtil.convertObjectToJsonBytes(dto))).andExpect(status().isOk()).andExpect(content().contentType(TestUtil.APPLICATION_JSON_UTF8)).andExpect(jsonPath("$.id", is(1))).andExpect(jsonPath("$.description", is("description"))).andExpect(jsonPath("$.title", is("title")));ArgumentCaptor<TodoDTO> dtoCaptor = ArgumentCaptor.forClass(TodoDTO.class);verify(todoServiceMock, times(1)).add(dtoCaptor.capture());verifyNoMoreInteractions(todoServiceMock);TodoDTO dtoArgument = dtoCaptor.getValue();assertNull(dtoArgument.getId());assertThat(dtoArgument.getDescription(), is("description"));assertThat(dtoArgument.getTitle(), is("title"));}
}

概要

现在,我们已经使用Spring MVC Test框架为REST API编写了单元测试。 本教程教会了我们四件事:

  • 我们学习了为控制器方法编写单元测试,这些方法从数据库中读取信息。
  • 我们学习了为控制器方法编写单元测试,这些方法将信息添加到数据库中。
  • 我们了解了如何将DTO对象转换为JSON字节并将转换结果发送到请求正文中。
  • 我们学习了如何使用JsonPath表达式编写JSON文档的断言。

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

参考: Spring MVC控制器的单元测试: Petri Kainulainen博客上的JCG合作伙伴Petri Kainulainen提供的REST API 。

翻译自: https://www.javacodegeeks.com/2013/08/unit-testing-of-spring-mvc-controllers-rest-api.html

spring

spring_Spring MVC控制器的单元测试:REST API相关推荐

  1. spring_Spring MVC控制器的单元测试:配置

    spring 传统上,为Spring MVC控制器编写单元测试既简单又成问题. 尽管编写调用控制器方法的单元测试非常简单,但问题是这些单元测试不够全面. 例如,我们不能仅通过调用已测试的控制器方法来测 ...

  2. Spring MVC控制器的单元测试:REST API

    Spring MVC提供了一种创建REST API的简便方法. 但是,为这些API编写全面而快速的单元测试一直很麻烦. Spring MVC测试框架的发布使我们可以编写可读,全面且快速的单元测试. 这 ...

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

    本教程的第一部分描述了如何配置使用Spring MVC Test框架的单元测试. 现在是时候动手做,学习如何为"常规"控制器编写单元测试了. 显而易见的下一个问题是: 什么是普通控 ...

  4. Spring MVC控制器的单元测试:配置

    传统上,为Spring MVC控制器编写单元测试既简单又成问题. 尽管编写调用控制器方法的单元测试非常简单,但问题是这些单元测试不够全面. 例如,我们不能仅通过调用已测试的控制器方法来测试控制器映射, ...

  5. Web API与MVC控制器的区别

    Web API属于ASP.NET核心平台的一部分,它利用MVC框架的底层功能方便我们快速的开发部署WEB服务. 我们可以在常规MVC应用通过添加API控制器来创建web api服务, 普通MVC应用程 ...

  6. 编写 Spring MVC 控制器的 14 个技巧

    欢迎关注方志朋的博客,回复"666"获面试宝典 通常,在Spring MVC中,我们编写一个控制器类来处理来自客户端的请求.然后,控制器调用业务类来处理与业务相关的任务,然后将客户 ...

  7. requestmapping默认是get还是post_编写Spring MVC控制器的14个技巧,你今天get到了吗?...

    Java面试笔试面经.Java技术每天学习一点 公众号Java面试 关注我不迷路 原文:http://dzone.com/articles/14-tips-for-writing-spring-mvc ...

  8. (转)基于MVC4+EasyUI的Web开发框架形成之旅--MVC控制器的设计

    http://www.cnblogs.com/wuhuacong/p/3284628.html 自从上篇<基于MVC4+EasyUI的Web开发框架形成之旅--总体介绍>总体性的概括,得到 ...

  9. Spring Controller – Spring MVC控制器

    Spring Controller annotation is a specialization of @Component annotation. Spring Controller annotat ...

最新文章

  1. linux中创建本地yum库,轻松安装Linux软件
  2. iOS pop至指定页面
  3. Python基础入门:反射
  4. Mozilla开发全新的公开网络API WebXR 来实现增强现实
  5. GPU Gems1 - 23 景深技术综述
  6. Java 8 的List<V> 转成 Map<K, V>
  7. Linux学习(四)- 文件查找和压缩
  8. java中sam接口_具有非SAM接口的lambda的Java习惯用法
  9. 数据库期末总结笔记( 零基础 )-第二章 关系数据库
  10. 第二章 拉普拉斯变换
  11. 专业计算机英语词汇翻译,计算机专业英语词汇与翻译复习题(已完成)
  12. 如何把iphone 6s通讯录导入到诺基亚E72i内
  13. 助力社区防疫,百数提供了一款管理系统模板
  14. 电脑安装系统时提示“找不到硬盘”如何解决
  15. 局域网电脑互访的设置大全
  16. L1-8 雀魂majsoul (20 分)
  17. 起风了,唯有努力以生存
  18. 宇宙的本源—存在之道和变化之道
  19. 30多个HTML5经典动画应用展示
  20. 联想网站诊断分析(12.10)

热门文章

  1. 安装高可用性虚拟机,livemigration系列之九
  2. ClassLoader引发的类型转换异常(转载)
  3. 你值得拥有:25个Linux性能监控工具
  4. Delphi下遍历文件夹下所有文件的递归算法
  5. JBPM使用assignHandler进行用户分派思路
  6. 【认证课程】NP 理论复习之ospf(一)
  7. 美国康普SYSTIMAX iPatch智能配线系统介绍
  8. 学习结构[记录]类型(9) - 变体结构
  9. 北大青鸟广州天河:高中生做技术经理!
  10. python3 多进程 multiprocessing 报错 AttributeError: ‘ForkAwareLocal‘ object has no attribute ‘connection‘