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 文档的痛点: 文档需 ...
随机推荐
- MIPI DSI之DBI DPI含义和区别(3-1)
一.MIPI MIPI(Mobile Industry Processor Interface/移动工业处理器接口)是2003年由ARM.Nokia.ST 等公司成立联盟并为移动应用处理器制定的一个开 ...
- 微信公众号开发C#系列-4、获取接口调用凭证
概述 获取接口调用凭证实质就是获取access_token.在微信接口开发中,许多服务的使用都离不开Access Token,Access Token相当于打开这些服务的钥匙,正常情况下会在7200秒 ...
- WebSocketSharp 的使用
Server 端示例代码: class Program { static void Main(string[] args) { var wssv = new WebSocketServer(" ...
- 你必须知道的.net读书笔记之第二回深入浅出关键字---对抽象编程:接口和抽象类
请记住,面向对象思想的一个最重要的原则就是:面向接口编程. 借助接口和抽象类,23个设计模式中的很多思想被巧妙的实现了,我认为其精髓简单说来就是:面向抽象编程. 抽象类应主要用于关系密切的对象,而接口 ...
- asp.net mvc前台显示带htm标签的解决办法(Razor —@Html.Raw())
数据是从后台富文本编辑后丢在数据库后取出的,不加Html.Raw(),前台就会把Html标签一同显示
- Yii2设计模式——注册树模式
应用举例 在Yii.php中: <?php class ServiceLocator extends Component { //保存实例化的对象,每个对象都是单例,且有唯一string类型的I ...
- 用Python写一个贪吃蛇
最近在学Python,想做点什么来练练手,命令行的贪吃蛇一般是C的练手项目,但是一时之间找不到别的,就先做个贪吃蛇来练练简单的语法. 由于Python监听键盘很麻烦,没有C语言的kbhit(),所以这 ...
- MyBatis学习---整合SpringMVC
[目录]
- node.js微信小程序配置消息推送
在开发微信小程序时,有一个消息推送,它的解释是这样的. 消息推送具体的内容是下面的这个网址 https://developers.weixin.qq.com/miniprogram/dev/fra ...
- There is already an object named '#xxxx' in the database.
这个案例是前几天同事遇到的一个案例,在存储过程中"删除"了一个临时表,然后重新创建这个临时表时遇到"There is already an object named 'x ...