写单元测试时,如果需要一个临时属性,但不能影响其他代码(即作用范围是当前UT类),有两种方式实现:
方式一:使用@SpringBootTest注解的properties属性
模块配置中,有一个test.prop属性:
test:
prop: 9527
在UT中做临时修改:
@SpringBootTest(properties = {"test.prop=testValue1"})
public class PropertiesAndArgsTest {
@Value("${test.prop}")
private String msg;
@Test
void testProperties(){
System.out.println(msg);
}
}
以上这个写法:比多环境开发中的测试环境影响范围更小,仅对当前测试类有效。
方式二:使用args属性
在启动测试环境时可以通过args参数设置测试环境专用的传入参数:
@SpringBootTest(args = {"--test.prop=testValue2"})
public class PropertiesAndArgsTest {
@Value("${test.prop}")
private String msg;
@Test
void testProperties(){
System.out.println(msg);
}
}
以上这个args,就类比命令行参数启动、在启动类的args上赋值,–server.port=8080这个格式,第一种方式则类比properties文件,因此,当这两种方式同时设置一个属性时,args的方式优先级更高,生效。 以上这个加载测试临时属性,亮点是不影响其他UT或者其他模块代码,仅对当前测试类生效。
源码中有一个Bean,定义了相关配置,如之前的MyBatisPlus的分页拦截器。那如何在UT里加一个配置Bean来辅助测试,且仅服务于这个UT类,如此,也不会引起配置冲突问题。先在test目录下定义一个配置Bean(肯定不能定义到com.src的实际源码里):
接下来使用@Import注解加载当前测试类专用的配置Bean
@SpringBootTest
@Import(MsgConfig.class)
public class ConfigurationTest {
@Autowired
private String msg;
@Test
void testConfiguration(){
System.out.println(msg);
}
}
平时写的测试Mapper和Service层方法的UT,就是一个普通的Java程序,没有web环境,想在web环境下启动,可以使用@SpringBootTest注解的webEnvironment
属性。
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class WebTest {
@Test
void testRandomPort () {
}
}
其中,属性取值可以为:使用源代码里定义的端口、随机端口、不启用web(默认)…
在测试类中开启Web环境后,接下来在UT中直接测Controller层的接口:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
//开启虚拟MVC调用
@AutoConfigureMockMvc
public class WebTest {
@Test
//注入虚拟MVC调用对象
public void testWeb(@Autowired MockMvc mvc) throws Exception {
//创建虚拟请求,当前访问/books
MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books");
//执行请求
ResultActions action = mvc.perform(builder);
}
}
以上并不是一次正真的调用,而是虚拟出来了一套Web环境,在这个虚拟环境中发起了这次调用。记得加@AutoConfigureMockMvc开启需求Mvc调用
,否则MockMvc这个Bean注入不上,至于注入写在形参里还是属性里,都行。
在发送完虚拟请求调用接口后,接下来对请求响应的状态做一个匹配(断言):
@Test
public void testSataus(@Autowired MockMvc mvc) throws Exception {
MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books/1");
ResultActions action = mvc.perform(builder);
//匹配执行状态(是否预期值)
//定义执行状态匹配器
StatusResultMatchers status = MockMvcResultMatchers.status();
//定义预期执行状态
ResultMatcher ok = status.isOk();
//使用本次真实执行结果与预期结果进行比对
action.andExpect(ok);
}
写个不存在的路径,执行结果:
和匹配状态一样,先看响应一个String的情况:
@Test
public void testSataus(@Autowired MockMvc mvc) throws Exception {
MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books/1");
ResultActions action = mvc.perform(builder);
//匹配执行结果(是否预期值)
//定义执行结果匹配器
ContentResultMatchers content = MockMvcResultMatchers.content();
//定义预期执行结果
ResultMatcher result = content.string("test1");
//使用本次真实执行结果与预期结果进行比对
action.andExpect(result);
}
当预期结果是一个json时:
ResultMatcher result = content.json("{\"id\":1,\"name\":\"SpringBoot2\"}");
大同小异,步骤一致:
@Test
public void testContentType(@Autowired MockMvc mvc) throws Exception {
MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books");
ResultActions action = mvc.perform(builder);
//匹配器
HeaderResultMatchers header = MockMvcResultMatchers.header();
//预期结果
ResultMatcher resultHeader = header.string("Content-Type", "application/json");
action.andExpect(resultHeader);
}
正常写UT时,这三个连一起就行,比如,先获取执行状态匹配器,再定义预期,然后做匹配。再响应头、响应结果…
在UT时,不让方法执行给数据库带来脏数据,可以加事务注解@Transactional,在有@SpringBootTest注解的情况下,Spring会识别到我们在做UT而回滚事务。
@SpringBootTest
@Transactional
public class DaoTest {
@Autowired
private BookService bookService;
@Test
void testSave(){
bookservice.save(new Book());
}
}
如果想在测试用例中提交事务,可以通过@Rollback
注解,并设置value属性为false:
@SpringBootTest
@Transactional
@Rollback(false) # 此时,会提交事务,即会影响数据库
public class DaoTest {
}
测试用例数据通常采用随机值进行测试,使用SpringBoot提供的随机数为其赋值,举个例子:
testcast:
book:
id: ${random.int} # 随机整数
id2: ${random.int(10)} # 10以内随机数
type: ${random.int(10,20)} # 10到20随机数
uuid: ${random.uuid} # 随机uuid
name: ${random.value} # 随机字符串,MD5字符串,32位
publishTime: ${random.long} # 随机整数(long范围)
注意:
定义个实体类绑定下随机生成的数据:
测试类看下效果:
@Autowired
private Book book;
@Test
void testData(){
System.out.println(book);
}
写懵了,刚开始竟然直接输出了一个new的Book对象,对象于Bean,前者不受Spring管控,也就是说Spring拿到随机数据也不能赋值给它,这个对象必须是Bean,所以改为了自动注入。