springboot提供了 spirng-boot-starter-test
以供开发者使用单元测试,在引入 spring-boot-starter-test
依赖后:html
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
复制代码
其中包含如下几个库:java
下面咱们将从Service层和Controller层的角度来简单介绍下单元测试mysql
在SpringBoot 2.0中,建立一个Service的单元测试,代码以下:web
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceImplTest {
@Autowired
private UserService userService;
@Test
public void insertUser() {
User user = new User();
user.setUsername("li ning");
user.setPassword("123456");
userService.insertUser(user);
}
}
复制代码
上面的测试很是简单,主要须要注意两个注解: @RunWith
和@SpringBootTest
spring
SpringRunner
,它实际上继承了 SpringJUnit4ClassRunner
类,而 SpringJUnit4ClassRunner
这个类是一个针对Junit 运行环境的自定义扩展,用来标准化在Springboot环境下Junit4.x的测试用例使用@SpringBootTest
的webEnvironment
属性定义运行环境:sql
首先建立一个Controller,代码以下:数据库
@RestController
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/user")
public String userMapping(@RequestBody User user){
userService.insertUser(user);
return "ok";
}
}
复制代码
而后建立Controller的单元测试,通常有两种建立方法。json
默认状况下,@SpringBootTest 不会启动服务器,若是需针对此模拟环境测试Web端点,能够以下配置 MockMvc:安全
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
public void userMapping() throws Exception {
String content = "{\"username\":\"pj_mike\",\"password\":\"123456\"}";
mockMvc.perform(MockMvcRequestBuilders.request(HttpMethod.POST, "/user")
.contentType("application/json").content(content))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().string("ok"));
}
}
复制代码
这里有一个 @AutoConfigureMockMvc注解,该注解表示启动测试的时候自动注入 MockMvc,而这个MockMvc有如下几个基本的方法:springboot
perform
: 执行一个RequestBuilder请求,会自动执行SpringMVC的流程并映射到相应的控制器执行处理。andExpect
: 添加RequsetMatcher验证规则,验证控制器执行完成后结果是否正确andDo
: 添加ResultHandler结果处理器,好比调试时打印结果到控制台andReturn
: 最后返回相应的MvcResult,而后进行自定义验证/进行下一步的异步处理这里有一个小技巧,通常来讲对于一个controller中每每有不止一个Request请求须要测试,敲打MockMvcRequestBuilders与MockMvcResultMatchers会显得比较繁琐,有一个简便的方法就是将这两个类的方法使用
import static
静态导入,而后就能够直接使用两个类的静态方法了。而后代码就变成以下所示:
...
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
public void userMapping() throws Exception {
String content = "{\"username\":\"pj_mike\",\"password\":\"123456\"}";
mockMvc.perform(request(HttpMethod.POST, "/user")
.contentType("application/json").content(content))
.andExpect(status().isOk())
.andExpect(content().string("ok"));
}
}
复制代码
另外,若是是只想关注Web层而不是启动完整的ApplicationContext,能够考虑使用 @WebMvcTest 注解,该注解不能与@SpringBootTest搭配使用,并且它只关注Web层面,至于涉及到数据层的时候,须要引入相关依赖,关于这个注解更多的介绍请参阅官方文档: docs.spring.io/spring-boot…
除了上面用 @AutoConfigureMockMvc 注解直接自动注入 MockMvc的方式,咱们还能够利用MockMvcBuilder来构建MockMvc对象,示例代码以下:
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserControllerTest4 {
@Autowired
private WebApplicationContext web;
private MockMvc mockMvc;
@Before
public void setupMockMvc() {
mockMvc = MockMvcBuilders.webAppContextSetup(web).build();
}
@Test
public void userMapping() throws Exception {
String content = "{\"username\":\"pj_m\",\"password\":\"123456\"}";
mockMvc.perform(request(HttpMethod.POST, "/user")
.contentType("application/json").content(content))
.andExpect(status().isOk())
.andExpect(content().string("ok"));
}
}
复制代码
在@SpringBootTest注解中设置属性 webEnvironment = WebEnvironment.RANDOM_PORT
,每次运行的时候会随机选择一个可用端口。咱们也能够还使用 @LoalServerPort
注解用于本地端口号。下面是测试代码:
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class UserControllerTest3 {
@Autowired
private TestRestTemplate testRestTemplate;
@Test
public void userMapping() throws Exception {
User user = new User();
user.setUsername("pj_pj");
user.setPassword("123456");
ResponseEntity<String> responseEntity = testRestTemplate.postForEntity("/user", user, String.class);
System.out.println("Result: "+responseEntity.getBody());
System.out.println("状态码: "+responseEntity.getStatusCodeValue());
}
}
复制代码
上面的代码中有一个关键的类——TestRestTemplate, TestRestTemplate是Spring的RestTemplate的一种替代品,可用于集成测试,更RestTemplate的使用功能方法相似,通常用于真实web环境测试中,关于该类更加详细的用法参考官方文档: docs.spring.io/spring-boot…
单元测试的时候,若是不想形成垃圾数据,能够开启事务功能,在方法或类头部添加 @Transactional
注解便可,在官方文档中对此也有说明:
If your test is @Transactional, it rolls back the transaction at the end of each test method by default. However, as using this arrangement with either RANDOM_PORT or DEFINED_PORT implicitly provides a real servlet environment, the HTTP client and server run in separate threads and, thus, in separate transactions. Any transaction initiated on the server does not roll back in this case
解读一下,在单元测试中使用 @Transactional
注解,默认状况下在测试方法的末尾会回滚事务。然而有一些特殊状况须要注意,当咱们使用 RANDOM_PORT
或DEFINED_PORT
这种安排隐式提供了一个真正的Servlet环境,因此HTTP客户端和服务器将在不一样的线程中运行,从而分离事务,这种状况下,在服务器上启动的任何事务都不会回滚。
固然若是你想关闭回滚,只要加上 @Rollback(false)
注解便可,@Rollback
表示事务执行完回滚,支持传入一个value,默认true即回滚,false不回滚。
还有一种状况须要注意,就是若是你使用的数据库是MySQL,有时候会发现加了注解 @Transactionl
也不会回滚,那么你就要查看一下你的默认引擎是否是InnoDB,若是不是就要改为 InnoDB。
MyISAM 与 InnoDB是mysql目前比较经常使用的两个数据库引擎,MyISAM与InnoDB的主要的不一样点在于性能和事务控制上,这里简单介绍下二者的区别与转换方法:
若是你的数据表是MyISAM引擎,因为它不支持事务,在单元测试中添加事务注解,测试方法也是不会回滚的。
修改默认引擎
mysql> show variables like '%storage_engine%';
复制代码
mysql> show create table user;
复制代码
mysql> ALTER TABLE user ENGINE=INNODB;
复制代码
注意
这里还有一点须要注意的地方,当咱们使用Spring Data JPA时,若是没有指定MySQL建表时的存储引擎,默认状况下会使用MySQL的MyISAM,这也是一个坑点,这种状况下,你在单元测试使用@Transactional
注解,回滚不会起做用。
解决方法是将 hibernate.dialect
属性配置成hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
,指定MySQL建表的时候使用 InnoDB引擎,示例配置文件以下:
spring:
jpa:
# 数据库类型
database: mysql
# 输出日志
show-sql: true
properties:
hibernate:
# JPA配置
hbm2ddl.auto: update
# mysql存储类型配置
dialect: org.hibernate.dialect.MySQL5InnoDBDialect
复制代码
上面简单总结了springboot下如何使用单元测试,关于单元测试更加详细的介绍请参阅官方文档:docs.spring.io/spring-boot…