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-path和json-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 Test框架,并为以下控制器方法编写单元测试:
- 第一个控制器方法返回待办事项列表。
- 第二种控制器方法返回单个待办事项的信息。
- 第三种控制器方法将新的待办事项条目添加到数据库,并返回添加的待办事项条目。
获取待办事项
第一个控制器方法返回从数据库中找到的待办事项列表。 让我们先来看一下该方法的实现。
预期行为
通过执行以下步骤来实现将所有待办事项返回到数据库的控制器方法:
- 它处理发送到url'/ api / todo'的GET请求。
- 它通过调用TodoService接口的findAll()方法获取Todo对象的列表。 此方法返回存储在数据库中的所有待办事项。 这些待办事项条目总是以相同的顺序返回。
- 它将接收到的列表转换为TodoDTO对象的列表。
- 它返回包含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"}
]
让我们继续并编写一个单元测试,以确保此控制器方法按预期工作。
测试:找到待办事项
通过执行以下步骤,我们可以为此控制器方法编写单元测试:
- 创建测试数据,该数据将在调用TodoService接口的findAll()方法时返回。 我们通过使用测试数据构建器类来创建测试数据。
- 配置我们的模拟对象,使其在调用findAll()方法时返回创建的测试数据。
- 执行GET请求以获取网址“ / api / todo”。
- 验证是否返回HTTP状态代码200。
- 确认响应的内容类型为“ application / json”,其字符集为“ UTF-8”。
- 使用JsonPath表达式$获取待办事项的集合,并确保返回两个待办事项。
- 通过使用JsonPath表达式$ [0] .id , $ [0] .description和$ [0] .title来获取第一个todo条目的id , 描述和标题 。 验证是否返回了正确的值。
- 通过使用JsonPath表达式$ [1] .id , $ [1] .description和$ [1] .title来获取第二个待办事项的ID , 描述和标题。 验证是否返回了正确的值。
- 验证TodoService接口的findAll()方法仅被调用一次。
- 确保在测试过程中没有调用模拟对象的其他方法。
我们的单元测试的源代码如下所示:
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") );
}
获取待办事项条目
我们必须测试的第二个控制器方法返回单个待办事项的信息。 让我们找出如何实现此控制器方法。
预期行为
通过执行以下步骤来实现返回单个待办事项信息的控制器方法:
- 它处理发送到url'/ api / todo / {id}'的GET请求。 {id}是一个路径变量,其中包含请求的待办事项条目的ID 。
- 它通过调用TodoService接口的findById()方法获取请求的待办事项条目,并将请求的待办事项条目的ID作为方法参数传递。 此方法返回找到的待办事项条目。 如果未找到待办事项条目,则此方法将抛出TodoNotFoundException 。
- 它将Todo对象转换为TodoDTO对象。
- 它返回创建的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");}
}
我们必须为此控制器方法编写两个单元测试:
- 我们必须编写一个测试,以确保在未找到todo条目时,我们的应用程序能够正常运行。
- 我们必须编写一个测试,以在找到待办事项条目时验证是否向客户端返回了正确的数据。
让我们看看如何编写这些测试。
测试1:找不到待办事项条目
首先,当找不到待办事项时,我们必须确保我们的应用程序正常运行。 我们可以按照以下步骤编写一个单元测试来确保这一点:
- 将模拟对象配置为在调用其findById()方法且请求的待办事项条目的ID为1L时引发TodoNotFoundException 。
- 执行GET请求以获取url'/ api / todo / 1'。
- 验证是否返回了HTTP状态代码404。
- 确保使用正确的方法参数(1L)仅调用一次TodoService接口的findById()方法。
- 验证在此测试期间没有调用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条目
其次,我们必须编写一个测试,以确保在找到请求的待办事项条目时返回正确的数据。 我们可以按照以下步骤编写测试来确保这一点:
- 创建Todo对象,该对象在调用我们的service方法时返回。 我们使用测试数据生成器创建此对象。
- 配置我们的模拟对象以在使用方法参数1L调用其findById()方法时返回创建的Todo对象。
- 执行GET请求以获取url'/ api / todo / 1'。
- 验证是否返回HTTP状态代码200。
- 确认响应的内容类型为“ application / json”,其字符集为“ UTF-8”。
- 通过使用JsonPath表达式$ .id获取待办事项的ID ,并验证ID为1。
- 使用JsonPath表达式$ .description获取待办事项的描述 ,并验证该描述是否为“ Lorem ipsum”。
- 通过使用JsonPath表达式$ .title获取待办事项的标题 ,并验证标题为“ Foo”。
- 确保使用正确的方法参数(1L)仅调用一次TodoService接口的findById()方法。
- 验证测试期间未调用模拟对象的其他方法。
我们的单元测试的源代码如下所示:
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);}
}
添加新的待办事项
第三种控制器方法将新的待办事项条目添加到数据库,并返回添加的待办事项条目的信息。 让我们继续前进,了解它是如何实现的。
预期行为
通过执行以下步骤来实现向数据库添加新的待办事项条目的控制器方法:
- 它处理发送到url'/ api / todo'的POST请求。
- 它验证作为方法参数给出的TodoDTO对象。 如果验证失败,则抛出MethodArgumentNotValidException 。
- 它通过调用TodoService接口的add()方法向数据库添加一个新的todo条目,并将TodoDTO对象作为方法参数传递。 此方法将新的待办事项添加到数据库,并返回添加的待办事项。
- 它将创建的Todo对象转换为TodoDTO对象。
- 它返回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.
}
如我们所见,该类声明了以下三个约束条件:
- 的描述的最大长度为500个字符。
- 待办事项的标题不能为空。
- 标题的最大长度为100个字符。
如果验证失败,我们的错误处理程序组件将确保
- HTTP状态代码400返回到客户端。
- 验证错误将作为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:验证失败
我们的第一个测试确保当添加的todo条目的验证失败时,我们的应用程序可以正常运行。 我们可以按照以下步骤编写此测试:
- 创建一个具有101个字符的标题 。
- 创建一个包含501个字符的描述 。
- 使用我们的测试数据构建器创建一个新的TodoDTO对象。 设置对象的标题和描述 。
- 执行POST请求以发送url'/ api / todo'。 将请求的内容类型设置为“ application / json”。 将请求的字符集设置为“ UTF-8”。 将创建的TodoDTO对象转换为JSON字节并将其发送到请求的正文中。
- 验证是否返回了HTTP状态代码400。
- 验证响应的内容类型为“ application / json”,并且其内容类型为“ UTF-8”。
- 通过使用JsonPath表达式$ .fieldErrors提取字段错误,并确保返回两个字段错误。
- 通过使用JsonPath表达式$ .fieldErrors [*]。path获取所有可用路径,并确保找到有关title和description字段的字段错误。
- 通过使用JsonPath表达式$ .fieldErrors [*]。message来获取所有可用的错误消息,并确保找到有关标题和描述字段的错误消息。
- 验证测试期间未调用模拟对象的方法。
我们的单元测试的源代码如下所示:
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条目已添加到数据库
第二个单元测试确保将新的待办事项添加到数据库时,控制器能够正常工作。 我们可以按照以下步骤编写此测试:
- 使用我们的测试数据构建器创建一个新的TodoDTO对象。 在标题和说明字段中设置“合法”值。
- 创建一个Todo对象,该对象在调用TodoService接口的add()方法时返回。
- 配置我们的模拟对象,使其在调用其add()方法并将TodoDTO对象作为参数时返回创建的Todo对象。
- 执行POST请求以发送url'/ api / todo'。 将请求的内容类型设置为“ application / json”。 将请求的字符集设置为“ UTF-8”。 将创建的TodoDTO对象转换为JSON字节并将其发送到请求的正文中。
- 验证是否返回HTTP状态代码200。
- 验证响应的内容类型为“ application / json”,并且其内容类型为“ UTF-8”。
- 使用JsonPath表达式$ .id获取返回的待办事项条目的ID ,并验证ID为1。
- 使用JsonPath表达式$ .description获取返回的待办事项的描述 ,并验证该描述是否为“描述”。
- 通过使用JsonPath表达式$ .title获取返回的待办事项条目的标题 ,并确保标题为“ title”。
- 创建一个ArgumentCaptor对象,该对象可以捕获TodoDTO对象。
- 验证TodoService接口的add()方法仅被调用一次,并捕获作为参数给定的对象。
- 验证测试期间未调用模拟对象的其他方法。
- 验证捕获的TodoDTO对象的ID为null。
- 验证所捕获的TodoDTO对象的描述是“说明”。
- 验证捕获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上获得 。 我建议您检查一下,因为它有很多单元测试,而本博客文章中未涉及。
翻译自: https://www.javacodegeeks.com/2013/08/unit-testing-of-spring-mvc-controllers-rest-api.html