Spring Boot 2.x 编写 RESTful API (五) 单元测试
概念
驱动模块
被测模块
桩模块
- 替代尚未开发完毕的子模块
- 替代对环境依赖较大的子模块 (例如数据访问层)
示例
测试 Service
@RunWith(SpringRunner.class)
@SpringBootTest
public class TvSeriesServiceTest {
@MockBean
TvSeriesDao tvSeriesDao;
@MockBean
TvCharacterDao tvCharacterDao;
@Autowired
TvSeriesService tvSeriesService;
@Test
public void testGetAll() {
List<TvSeries> list = new ArrayList<>();
TvSeries ts = new TvSeries();
ts.setName("POI");
list.add(ts);
// 告诉 mock 当执行 getAll 方法时,返回上面创建的 list
Mockito.when(tvSeriesDao.getAll()).thenReturn(list);
List<TvSeries> result = tvSeriesService.getAllTvSeries();
Assert.assertTrue(result.size() == list.size());
Assert.assertTrue("POI".equals(result.get(0).getName()));
}
@Test
public void testGetOne() {
//根据不同的传入参数,被 mock 的 bean 返回不同的数据的例子
String newName = "Person Of Interest";
BitSet mockExecuted = new BitSet();
Mockito.doAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
Object[] args = invocation.getArguments();
TvSeries bean = (TvSeries) args[0];
//传入的值,应该和下面调用 tvSeriesService.updateTvSeries() 方法时的参数中的值相同
Assert.assertEquals(newName, bean.getName());
mockExecuted.set(0);
return 1;
}
}).when(tvSeriesDao).update(any(TvSeries.class));
TvSeries ts = new TvSeries();
ts.setName(newName);
ts.setId(111);
tvSeriesService.updateTvSeries(ts);
Assert.assertTrue(mockExecuted.get(0));
}
}
测试 Controller
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc // 初始化一个 mvc 环境用于测试
public class TvSeriesControllerTest {
@MockBean
TvSeriesDao tvSeriesDao;
@MockBean
TvCharacterDao tvCharacterDao;
@Autowired
private MockMvc mockMvc;
@Autowired
private TvSeriesController tvSeriesController;
@Test
public void testGetAll() throws Exception {
List<TvSeries> list = new ArrayList<>();
TvSeries ts = new TvSeries();
ts.setName("POI");
list.add(ts);
Mockito.when(tvSeriesDao.getAll()).thenReturn(list);
// 相当于在启动项目后,执行 GET /tvseries,被测模块是 web 控制层,因为 web 控制层会调用业务逻辑层,所以业务逻辑层也会被测试
// 业务逻辑层调用了被 mock 出来的数据访问层桩模块。
//如果想仅仅测试 web 控制层,(例如业务逻辑层尚未编码完毕),可以 mock 一个业务逻辑层的桩模块
mockMvc.perform(MockMvcRequestBuilders.get("/tvseries"))
.andDo(MockMvcResultHandlers.print())
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().string(Matchers.containsString("POI")));
}
@Test
public void testAddSeries() throws Exception{
BitSet bitSet = new BitSet(1);
bitSet.set(0, false);
Mockito.doAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
Object[] args = invocation.getArguments();
TvSeries ts = (TvSeries) args[0];
Assert.assertEquals(ts.getName(), "疑犯追踪");
ts.setId(5432);
bitSet.set(0, true);
return 1;
}
}).when(tvSeriesDao).insert(Mockito.any(TvSeries.class));
Mockito.doAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
Object[] args = invocation.getArguments();
TvCharacter tc = (TvCharacter) args[0];
// json 中传递过来的剧中角色名字
Assert.assertEquals(tc.getName(), "芬奇");
Assert.assertTrue(tc.getTvSeriesId() == 5432);
bitSet.set(0, true);
return 1;
}
}).when(tvCharacterDao).insert(Mockito.any(TvCharacter.class));
String jsonData = "{\"name\":\"疑犯追踪\",\"seasonCount\":5,\"originRelease\":\"2011-09-22\",\"tvCharacters\":[{\"name\":\"芬奇\"}]}";
this.mockMvc.perform(MockMvcRequestBuilders.post("/tvseries")
.contentType(MediaType.APPLICATION_JSON).content(jsonData))
.andDo(MockMvcResultHandlers.print())
.andExpect(MockMvcResultMatchers.status().isOk());
Assert.assertTrue(bitSet.get(0));
}
@Test
public void testFileUpload() throws Exception{
String fileFolder = "target/files/";
File folder = new File(fileFolder);
if(!folder.exists()) {
folder.mkdirs();
}
// 设置 bean 里面通过 @Value 获得的数据
ReflectionTestUtils.setField(tvSeriesController, "uploadFolder", folder.getAbsolutePath());
InputStream is = getClass().getResourceAsStream("/testfileupload.jpg");
if(is == null) {
throw new RuntimeException("需要先在src/test/resources目录下放置一张jpg文件,名为testfileupload.jpg然后运行测试");
}
//模拟一个文件上传的请求
MockMultipartFile imgFile = new MockMultipartFile("photo", "testfileupload.jpg", "image/jpeg", IOUtils.toByteArray(is));
ResultActions result = mockMvc.perform(MockMvcRequestBuilders.multipart("/tvseries/1/photos")
.file(imgFile))
.andExpect(MockMvcResultMatchers.status().isOk());
//解析返回的 JSON
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> map = mapper.readValue(result.andReturn().getResponse().getContentAsString(), new TypeReference<Map<String, Object>>(){});
String fileName = (String) map.get("photo");
File f2 = new File(folder, fileName);
//返回的文件名,应该已经保存在 filFolder 文件夹下
Assert.assertTrue(f2.exists());
}
}
Spring Boot 2.x 编写 RESTful API (五) 单元测试的更多相关文章
- Spring Boot 2.x 编写 RESTful API (六) 事务
用Spring Boot编写RESTful API 学习笔记 Transactional 判定顺序 propagation isolation 脏读 不可重复读 幻读 不可重复读是指记录不同 (upd ...
- Spring Boot 2.x 编写 RESTful API (四) 使用 Mybatis
用Spring Boot编写RESTful API 学习笔记 添加依赖 <dependency> <groupId>org.mybatis.spring.boot</gr ...
- Spring Boot 2.x 编写 RESTful API (三) 程序层次 & 数据传输
用Spring Boot编写RESTful API 学习笔记 程序的层次结构 相邻层级的数据传输 JavaBean 有一个 public 的无参构造方法 属性 private,且可以通过 get.se ...
- Spring Boot 2.x 编写 RESTful API (二) 校验
用Spring Boot编写RESTful API 学习笔记 约束规则对子类依旧有效 groups 参数 每个约束用注解都有一个 groups 参数 可接收多个 class 类型 (必须是接口) 不声 ...
- Spring Boot 2.x 编写 RESTful API (一) RESTful API 介绍 & RestController
用Spring Boot编写RESTful API 学习笔记 RESTful API 介绍 REST 是 Representational State Transfer 的缩写 所有的东西都是资源,所 ...
- Spring Boot 集成 Swagger 生成 RESTful API 文档
原文链接: Spring Boot 集成 Swagger 生成 RESTful API 文档 简介 Swagger 官网是这么描述它的:The Best APIs are Built with Swa ...
- Spring Boot 集成Swagger2生成RESTful API文档
Swagger2可以在写代码的同时生成对应的RESTful API文档,方便开发人员参考,另外Swagger2也提供了强大的页面测试功能来调试每个RESTful API. 使用Spring Boot可 ...
- Spring Boot中使用Swagger2构建API文档
程序员都很希望别人能写技术文档,自己却很不愿意写文档.因为接口数量繁多,并且充满业务细节,写文档需要花大量的时间去处理格式排版,代码修改后还需要同步修改文档,经常因为项目时间紧等原因导致文档滞后于代码 ...
- Spring Boot 项目学习 (四) Spring Boot整合Swagger2自动生成API文档
0 引言 在做服务端开发的时候,难免会涉及到API 接口文档的编写,可以经历过手写API 文档的过程,就会发现,一个自动生成API文档可以提高多少的效率. 以下列举几个手写API 文档的痛点: 文档需 ...
随机推荐
- 基于Log4Net本地日志服务简单实现
背景 项目开发中,我们或多或少会使用诸如NLog,Log4Net,Kafka+ELK等等日志套件: 基于关注点分离原则,业务开发的时候不应该关注日志具体实现:并且后续能方便切换其他日志套件: 这里先实 ...
- Spring基础系列-Web开发
原创作品,可以转载,但是请标注出处地址:https://www.cnblogs.com/V1haoge/p/9996902.html SpringBoot基础系列-web开发 概述 web开发就是集成 ...
- vue中用mock制造模拟接口(本文主要解决坑),一定要看完哦
最近新入职一家公司,后端造接口速度很慢,想来想去还是搞一套模拟接口,来满足开发需求,有人会问,我造一个死数据不就可以了吗?或者说,后端数据结构都没出来,字段我怎么定? 问这个问题的人不奇怪,我之前也有 ...
- RAID磁盘阵列及CentOS7系统启动流程(week2_day3)--技术流ken
RAID概念 磁盘阵列(Redundant Arrays of Independent Disks,RAID),有“独立磁盘构成的具有冗余能力的阵列”之意. 磁盘阵列是由很多价格较便宜的磁盘,以硬件( ...
- Linux高级命令进阶(week1_day2)--技术流ken
输出重定向 场景:一般命令的输出都会显示在终端中,有些时候需要将一些命令的执行结果想要保存到文件中进行后续的分析/统计,则这时候需要使用到的输出重定向技术. >:覆盖输出,会覆盖掉原先的文件内容 ...
- Linux高级运维 第四章 文件的基本管理和XFS文件系统备份恢复
4.1 Linux系统目录结构和相对/绝对路径 4.1.1系统目录结构 在windows系统中,查看文件先进入相应的盘符,然后进入文件目录 在windows中,它是多根 c:\ d:\ e ...
- Huffman Tree 简单构造
//函数:构造Huffman树HT[2*n-1] #define MAXVALUE 9999//假设权值不超过9999 #define MAXLEAF 30 #define MAXNODE MAXLE ...
- 委托的多线程方法BeginInvoke
同步方法和异步方法: 同步方法调用在程序继续执行之前需要等待同步方法执行完毕返回结果.(比如烧水泡茶,需要等水烧开了才能继续泡茶) 异步方法则在被调用之后立即返回以便程序在被调用方法完成其任务的同时执 ...
- setTimeout与setInterval的区别浅析
在网页制作动态效果时,一定会遇到某些需求,要求某段程序等待多时时间后再开始执行,就像在我们的生活中一样,待会儿再开始做一件事.在JavaScript中主要通过定时器实现此类需求,本文将对定时器做一个概 ...
- 在虚拟机中搭建qduoj(一)——准备工作
为缩减篇幅,已略去ubuntu镜像下载.虚拟机(VirtualBox.VMware)安装等操作,若有疑问请搜索相关教程. 1.虚拟机系统安装 本教程使用Ubuntu16.04 server版本(des ...