本文首发于个人网站:Spring Boot项目中使用Mockito

Spring Boot可以和大部分流行的测试框架协同工作:通过Spring JUnit创建单元测试;生成测试数据初始化数据库用于测试;Spring Boot可以跟BDD(Behavier Driven Development)工具、Cucumber和Spock协同工作,对应用程序进行测试。

进行软件开发的时候,我们会写很多代码,不过,再过六个月(甚至一年以上)你知道自己的代码怎么运作么?通过测试(单元测试、集成测试、接口测试)可以保证系统的可维护性,当我们修改了某些代码时,通过回归测试可以检查是否引入了新的bug。总得来说,测试让系统不再是一个黑盒子,让开发人员确认系统可用。

在web应用程序中,对Controller层的测试一般有两种方法:(1)发送http请求;(2)模拟http请求对象。第一种方法需要配置回归环境,通过修改代码统计的策略来计算覆盖率;第二种方法是比较正规的思路,但是在我目前经历过的项目中用得不多,今天总结下如何用Mock对象测试Controller层的代码。

在之前的几篇文章中,我们都使用bookpub这个应用程序作为例子,今天也不例外,准备测试它提供的RESTful接口是否能返回正确的响应数据。这种测试不同于单元测试,需要为之初始化完整的应用程序上下文、所有的spring bean都织入以及数据库中需要有测试数据,一般来说这种测试称之为集成测试或者接口测试

实战

通过spirng.io新建的Spring Boot项目提供了一个空的测试文件——BookPubApplicationTest.java,内容是:

  1. @RunWith(SpringJUnit4ClassRunner.class)
  2. @SpringApplicationConfiguration(classes = BookPubApplication.class)
  3. public class BookPubApplicationTests {
  4. @Test
  5. public void contextLoads() {
  6. }
  7. }
  • 在pom文件中增加spring-boot-starter-test依赖,添加jsonPath依赖
  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-test</artifactId>
  4. <scope>test</scope>
  5. </dependency>
  6. <dependency>
  7. <groupId>com.jayway.jsonpath</groupId>
  8. <artifactId>json-path</artifactId>
  9. </dependency>
  • 在BookPubApplicationTest中添加测试用例
  1. package com.test.bookpub;
  2. import com.test.bookpub.domain.Book;
  3. import com.test.bookpub.repository.BookRepository;
  4. import org.junit.Before;import org.junit.Test;
  5. import org.junit.runner.RunWith;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.beans.factory.annotation.Value;
  8. import org.springframework.boot.test.SpringApplicationConfiguration;
  9. import org.springframework.boot.test.TestRestTemplate;
  10. import org.springframework.boot.test.WebIntegrationTest;
  11. import org.springframework.http.MediaType;
  12. import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
  13. import org.springframework.test.web.servlet.MockMvc;
  14. import org.springframework.test.web.servlet.setup.MockMvcBuilders;
  15. import org.springframework.web.client.RestTemplate;
  16. import org.springframework.web.context.WebApplicationContext;
  17. import static org.junit.Assert.assertEquals;
  18. import static org.junit.Assert.assertNotNull;
  19. import static org.hamcrest.Matchers.containsString;
  20. import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
  21. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
  22. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
  23. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
  24. @RunWith(SpringJUnit4ClassRunner.class)
  25. @SpringApplicationConfiguration(classes = BookPubApplication.class)
  26. @WebIntegrationTest("server.port:0")
  27. public class BookPubApplicationTests {
  28. @Autowired
  29. private WebApplicationContext context;
  30. @Autowired
  31. private BookRepository bookRepository;
  32. @Value("${local.server.port}")
  33. private int port;
  34. private MockMvc mockMvc;
  35. private RestTemplate restTemplate = new TestRestTemplate();
  36. @Before
  37. public void setupMockMvc() {
  38. mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
  39. }
  40. @Test
  41. public void contextLoads() {
  42. assertEquals(1, bookRepository.count());
  43. }
  44. @Test
  45. public void webappBookIsbnApi() {
  46. Book book = restTemplate.getForObject("http://localhost:" + port +"/books/9876-5432-1111", Book.class);
  47. assertNotNull(book);
  48. assertEquals("中文测试", book.getPublisher().getName());
  49. }
  50. @Test
  51. public void webappPublisherApi() throws Exception {
  52. //MockHttpServletRequestBuilder.accept方法是设置客户端可识别的内容类型
  53. //MockHttpServletRequestBuilder.contentType,设置请求头中的Content-Type字段,表示请求体的内容类型
  54. mockMvc.perform(get("/publishers/1")
  55. .accept(MediaType.APPLICATION_JSON_UTF8))
  56. .andExpect(status().isOk())
  57. .andExpect(content().string(containsString("中文测试")))
  58. .andExpect(jsonPath("$.name").value("中文测试"));
  59. }
  60. }
  • spring boot项目的代码覆盖率

    使用cobertura,参考项目的github地址:spring boot template
  1. # To create test coverage reports (in target/site/cobertura)
  2. mvn clean cobertura:cobertura test



分析

首先分析在BookPubApplicationTests类中用到的注解:

  • @RunWith(SpringJUnit4ClassRunner.class),这是JUnit的注解,通过这个注解让SpringJUnit4ClassRunner这个类提供Spring测试上下文。
  • @SpringApplicationConfiguration(classes = BookPubApplication.class),这是Spring Boot注解,为了进行集成测试,需要通过这个注解加载和配置Spring应用上下文。这是一个元注解(meta-annoation),它包含了@ContextConfiguration( loader = SpringApplicationContextLoader.class)这个注解,测试框架通过这个注解使用Spring Boot框架的SpringApplicationContextLoader加载器创建应用上下文。
  • @WebIntegrationTest("server.port:0"),这个注解表示当前的测试是集成测试(integration test),因此需要初始化完整的上下文并启动应用程序。这个注解一般和@SpringApplicationConfiguration一起出现。server.port:0指的是让Spring Boot在随机端口上启动Tomcat服务,随后在测试中程序通过@Value("${local.server.port}")获得这个端口号,并赋值给port变量。当在Jenkins或其他持续集成服务器上运行测试程序时,这种随机获取端口的能力可以提供测试程序的并行性。

了解完测试类的注解,再看看测试类的内部。由于这是Spring Boot的测试,因此我们可通过@Autowired注解织入任何由Spring管理的对象,或者是通过@Value设置指定的环境变量的值。在现在这个测试类中,我们定义了WebApplicationContextBookRepository对象。

每个测试用例用@Test注解修饰。在第一个测试用例——contextLoads()方法中,我仅仅需要确认BookRepository连接已经建立,并且数据库中已经包含了对应的测试数据。

第二个测试用例用来测试我们提供的RESTful URL——通过ISBN查询一本书,即“/books/{isbn}”。在这个测试用例中我们使用TestRestTemplate对象发起RESTful请求。

第三个测试用例中展示了如何通过MockMvc对象实现跟第二个测试类似的功能。Spring测试框架提供MockMvc对象,可以在不需要客户端-服务端请求的情况下进行MVC测试,完全在服务端这边就可以执行Controller的请求,跟启动了测试服务器一样。

测试开始之前需要建立测试环境,setup方法被@Before修饰。通过MockMvcBuilders工具,使用WebApplicationContext对象作为参数,创建一个MockMvc对象。

MockMvc对象提供一组工具函数用来执行assert判断,都是针对web请求的判断。这组工具的使用方式是函数的链式调用,允许程序员将多个测试用例链接在一起,并进行多个判断。在这个例子中我们用到下面的一些工具函数:

  • perform(get(...))建立web请求。在我们的第三个用例中,通过MockMvcRequestBuilder执行GET请求。
  • andExpect(...)可以在perform(...)函数调用后多次调用,表示对多个条件的判断,这个函数的参数类型是ResultMatcher接口,在MockMvcResultMatchers这这个类中提供了很多返回ResultMatcher接口的工具函数。这个函数使得可以检测同一个web请求的多个方面,包括HTTP响应状态码(response status),响应的内容类型(content type),会话中存放的值,检验重定向、model或者header的内容等等。这里需要通过第三方库json-path检测JSON格式的响应数据:检查json数据包含正确的元素类型和对应的值,例如jsonPath("$.name").value("中文测试")用于检查在根目录下有一个名为name的节点,并且该节点对应的值是“中文测试”。

一个字符乱码问题

  • 问题描述:通过spring-boot-starter-data-rest建立的repository,取出的汉字是乱码。

  • 分析:使用postman和httpie验证都没问题,说明是Mockmvc的测试用例写得不对,应该主动设置客户端如何解析HTTP响应,用get.accept方法设置客户端可识别的内容类型,修改后的测试用例如下:

  1. @Test
  2. public void webappPublisherApi() throws Exception {
  3. //MockHttpServletRequestBuilder.accept方法是设置客户端可识别的内容类型
  4. //MockHttpServletRequestBuilder.contentType,设置请求头中的Content-Type字段,表示请求体的内容类型
  5. mockMvc.perform(get("/publishers/1")
  6. .accept(MediaType.APPLICATION_JSON_UTF8))
  7. .andExpect(status().isOk())
  8. .andExpect(content().string(containsString("中文测试")))
  9. .andExpect(jsonPath("$.name").value("中文测试"));
  10. }

参考资料

  1. 基于Spring-WS的Restful API的集成测试
  2. J2EE要懂的小事—图解HTTP协议
  3. Integration Testing a Spring Boot Application
  4. spring boot project template

Spring Boot 1.x系列

  1. Spring Boot的自动配置、Command-line-Runner
  2. 了解Spring Boot的自动配置
  3. Spring Boot的@PropertySource注解在整合Redis中的使用
  4. Spring Boot项目中如何定制HTTP消息转换器
  5. Spring Boot整合Mongodb提供Restful接口
  6. Spring中bean的scope
  7. Spring Boot项目中使用事件派发器模式
  8. Spring Boot提供RESTful接口时的错误处理实践
  9. Spring Boot实战之定制自己的starter
  10. Spring Boot项目如何同时支持HTTP和HTTPS协议
  11. 自定义的Spring Boot starter如何设置自动配置注解

本号专注于后端技术、JVM问题排查和优化、Java面试题、个人成长和自我管理等主题,为读者提供一线开发者的工作和成长经验,期待你能在这里有所收获。

Spring Boot项目中使用Mockito的更多相关文章

  1. 在Spring Boot项目中使用Spock测试框架

    本文首发于个人网站:在Spring Boot项目中使用Spock测试框架 Spock框架是基于Groovy语言的测试框架,Groovy与Java具备良好的互操作性,因此可以在Spring Boot项目 ...

  2. Spring Boot项目中如何定制拦截器

    本文首发于个人网站:Spring Boot项目中如何定制拦截器 Servlet 过滤器属于Servlet API,和Spring关系不大.除了使用过滤器包装web请求,Spring MVC还提供Han ...

  3. Spring Boot项目中如何定制PropertyEditors

    本文首发于个人网站:Spring Boot项目中如何定制PropertyEditors 在Spring Boot: 定制HTTP消息转换器一文中我们学习了如何配置消息转换器用于HTTP请求和响应数据, ...

  4. Spring Boot项目中如何定制servlet-filters

    本文首发于个人网站:Spring Boot项目中如何定制servlet-filters 在实际的web应用程序中,经常需要在请求(request)外面增加包装用于:记录调用日志.排除有XSS威胁的字符 ...

  5. 在Spring Boot项目中使用Spock框架

    转载:https://www.jianshu.com/p/f1e354d382cd Spock框架是基于Groovy语言的测试框架,Groovy与Java具备良好的互操作性,因此可以在Spring B ...

  6. 你真的理解 Spring Boot 项目中的 parent 吗?

    前面和大伙聊了 Spring Boot 项目的三种创建方式,这三种创建方式,无论是哪一种,创建成功后,pom.xml 坐标文件中都有如下一段引用: <parent> <groupId ...

  7. Spring Boot项目中使用Swagger2

    Swagger2是一款restful接口文档在线生成和在线接口调试工具,Swagger2在Swagger1.x版本的基础上做了些改进,下面是在一个Spring Boot项目中引入Swagger2的简要 ...

  8. Spring Boot2 系列教程(三)理解 Spring Boot 项目中的 parent

    前面和大伙聊了 Spring Boot 项目的三种创建方式,这三种创建方式,无论是哪一种,创建成功后,pom.xml 坐标文件中都有如下一段引用: <parent> <groupId ...

  9. Spring Boot 项目中的 parent

    前言 我们成功创建Spring Boot之后,pom.xml坐标文件中都会有如下一段引用: <parent> <groupId>org.springframework.boot ...

随机推荐

  1. Day4 文件管理-常用命令

    文件管理 --> 创建 移动 删除 复制 1.cp复制: #####-v:详细显示命令执行的操作 #####-r: 递归处理目录与子目录 #####-p: 保留源文件或目录的属性 #####1. ...

  2. spring中使用aop配置事务

    spring的事务配置有5种方式,这里记录其中的一种:基于tx/aop声明式事务配置 在之前spring aop介绍和示例这篇所附代码的基础上进行测试 一.添加save方法 1.在testDao类里添 ...

  3. aclocal-1.14: 未找到命令

    WARNING: 'aclocal-1.14' is missing on your system. 一条命令解决 autoreconf -ivf

  4. 29道Zookeeper面试题超详细(附答案)

    原文链接 ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件.它是一个为分布式应用提供一致性服务的软件 ...

  5. 【linux】【docker】docker私服安装

    前言 系统环境:Centos7.jdk1.8 docker私服:可以把项目通过dockerfile文件build成docker镜像,供其他环境拉取.部署在本地,私有化. 安装 dockerHUB私服 ...

  6. Fliptile POJ-3279 DFS

    题目链接:Fliptile 题目大意 有一个01矩阵,每一次翻转(0->1或者1->0)一个元素,就会把与他相邻的四个元素也一起翻转.求翻转哪些元素能用最少的步骤,把矩阵变成0矩阵. 思路 ...

  7. Kerberoasting攻击

    前面我们介绍了<Windows本地认证>.<Windows网络认证>.<Windows域认证>和<SPN扫描>,这次继续讲解域内相关的东西. 0x01介 ...

  8. 记一次jmeter从txt文本获取数值并给测试计划的变量赋值,jmeter永久性修改变量。

    前言: 需要永久性的改变变量. 其实这个办法并不是最好的,但是是最容易实现的.后期可做成从数据库里直接取值. 赋值BeanShell import java.io.File; import java. ...

  9. 读《深入理解Elasticsearch》点滴-过滤器

    1.过滤器不影响文档得分 2.过滤的唯一目的是用特定筛选条件来缩小结果范围:而查询不仅缩小结果范围,还会影响文档的得分 3.过滤器运行更加高效(因为不用计算得分) 4.通常过滤器使用Bits接口,返回 ...

  10. Jquery 动态添加元素 添加点击事件

    给动态添加的元素添加js事件,不能直接添加js事件,需要借助on( )方法,给选择器指定的子元素添加事件处理函数,并非选择器本身: 1. 语法 $(selector).on(event,childSe ...