本文主要介绍Spring Boot的测试框架——spring-boot-starter-test模块,主要内容分为两块:
-
第一部分是与JUnit测试的集成:
-
第二部分介绍了与测试相关的一系列注解:
1. Spring Boot的核心测试模块
org.springframework.boot
spring-boot-starter-test
test
2. Junit4和Junit5
Spring Boot版本在2.4之后就不支持JUnit4了。
首先是2.3.12.RELEASE依赖:mvnrepository地址
可以看到在2.4以前,除了引入了junit-jupiter之外,还引用了junit-vintage-engine。
而junit-vintage-engine最大的特点就是它运行JUnit 4引擎测试,mvnrepository地址
再看2.4.0依赖:mvnrepository地址
而junit-juspiter用的是JUnit 5引擎测试,mvnrepository地址
总结下就是:
- vintage的意思是古典的,即使用的是junit4的引擎
- jupiter的意思是木星,是junit5的group id,即junit-jupiter。
Spring Boot Release | 引用的Junit | 引擎 | 运行器/功能扩展器 |
---|---|---|---|
2.3.12.RELEASE | junit-jupiter, junit-vintage-engine | junit 4.13(junit4引擎测试) | @RunWith(SpringRunner.class) |
2.4.0(+) | junit-jupiter | junit-jupiter-engine(junit 5引擎测试) | @ExtendWith(SpringExtension.class) |
如果在Spring Boot高版本(>=2.4.0)使用Junit4,那么需要手动加上junit-vintage-engine依赖。
Spring Boot高版本(>=2.4.0)不需要在@SpringBootTest加上之后额外再加上@ExtendWith(SpringExtension.class) 的原因是@SpringBootTest已经帮我们加上了,具体看@SpringBootTest v2.4.0版本API
3. 使用@SpringBootTest集成测试
@SpringBootTest
注解是用来运行Spring整个容器的,它会创建一个ApplicationContext(主要是通过查找注解@SpringBootApplication
入口,即Spring Boot启动类)。
3.1 测试类中可以使用@Autowired
拿到相应的bean
@SpringBootTest
public class UserServiceTest {
@Autowired
private UserService userService;
@Test
public void getUserService() {
Assertions.assertNotNull(userService);
}
}
3.2 @SpringBootTest
注解还可以指定WebEnvironment
首先新建一个API,/version,返回“v1.0”。
测试类,使用RANDOM_PORT会随机生成端口,可以有效的避免端口冲突:
- 使用
@LocalServerPort
获取到随机生成的端口。 - 默认注入了
TestRestTemplate
,可以直接拿来使用。
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class VersionControllerTest {
@LocalServerPort
private int port;
@Autowired
private TestRestTemplate testRestTemplate;
@Test
public void test() {
String version = testRestTemplate.getForObject("http://localhost:" + port + "/version", String.class);
Assertions.assertEquals("v1.0", version);
}
}
3.3 使用@TestPropertySource
读取测试配置
默认的配置 - application.properties:
hello.name=spring boot test
hello.env=dev
Test独有的配置 - test.properties:
hello.env=test
hello.testonly=true
@TestPropertySource
读取配置后,优先级是最高的,比如hello.env最后的值是test,而不是dev。
@SpringBootTest
@TestPropertySource(locations = "classpath:test.properties")
public class PropertySourceTest {
@Value(value = "${hello.env}")
private String helloEnv;
@Value(value = "${hello.name}")
private String helloName;
@Value(value = "${hello.testonly}")
private boolean helloTestOnly;
@Test
public void test() {
Assertions.assertEquals("test", helloEnv);
Assertions.assertEquals("spring boot test", helloName);
Assertions.assertEquals(true, helloTestOnly);
}
}
4. Test Configuration:@TestConfiguration
首先创建一个简单的Class类,叫Course,属性有id和name。
4.1 @TestConfiguration
作为内部类
通过@TestConfiguration创建的Bean,作用范围在test里,不会影响到正式环境。通过@Autowired可以拿到创建的bean:
@SpringBootTest
public class CourseInnerClassTest {
@TestConfiguration
static class CourseConfig {
@Bean
public Course course() {
return Course.builder().id(1).name("test01").build();
}
}
@Autowired
private Course course;
@Test
public void test() {
Assertions.assertEquals(1, course.getId());
Assertions.assertEquals("test01", course.getName());
}
}
4.2 @TestConfiguration
作为独立的配置类
@TestConfiguration
public class CourseConfig {
@Bean
public Course course2() {
return Course.builder().id(2).name("test02").build();
}
}
测试,使用@Import
把配置类导入进来,或者也可使用@SpringBootTest(classes = CourseConfig.class)
导入配置类:
@SpringBootTest
@Import(CourseConfig.class)
public class CourseSeparateClassTest {
@Autowired
private Course course2;
@Test
public void test() {
Assertions.assertEquals(2, course2.getId());
Assertions.assertEquals("test02", course2.getName());
}
}
另外,如果@Configuration有定义的bean,在@TestConfiguration中想要覆盖,可以预先开启spring.main.allow-bean-definition-overriding=true
,这样就可以使用@TestConfiguration覆盖@Configuration中的bean了。
5. 与Mockito结合使用——@MockBean
Spring Boot Starter Test引入了mockito框架:
使用@MockBean
注解来生成一个mock的bean,我们可以使用Mockito.when来模拟一些方法(比如Mock Jpa的Repository的find方法,这样就算数据库里的数据还没有准备好,我们也可以自己模拟数据了。)
@SpringBootTest
public class CourseServiceTest {
@MockBean
private CourseService courseService;
@Test
public void test() {
Mockito.when(courseService.getName()).thenReturn("mock name");
Assertions.assertEquals("mock name", courseService.getName());
}
}
6. 使用@DataJpaTest 注解测试JPA
首先需要加入h2的依赖,scope是test:
com.h2database
h2
2.1.212
test
@DataJpaTest
注解主要关注的是JPA的component。使用这个注解不会开始全部的auto-configuration,而是只会生效JPA相关的测试。(这就意味着上述的CourseService并不会成功的被Autowired。)
@DataJpaTest会配置:
- 默认情况下,带有@DataJpaTest注解的测试使用嵌入式内存数据库。会基于h2生成自己的DataSource,如果别的什么都没有配,就是加了这个注解,运行的话,可以看到h2的JDBC Url也是随机生成的。
- 除了DataSourse,还会生成Hibernate以及Spring Data相关的配置(如TestEntityManager,或是下文的UserRepository)。
- 会自动加上@EntityScan,自动扫描Hibernate相关的Entity。
- 开启SQL日志(类似spring.jpa.show-sql=true)
启动日志,H2 JDBC Url是随机的:
2022-05-28 20:14:09.241 INFO 14181 --- [ main] o.s.j.d.e.EmbeddedDatabaseFactory : Starting embedded database: url='jdbc:h2:mem:6d67e11c-0e76-415f-a6e4-11825b7c214a;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=false', username='sa'
以下是测试示例,我加了@TestPropertySource
来set了sql的格式,让他可以换行显示。
@DataJpaTest
@TestPropertySource(properties = {
"spring.jpa.properties.hibernate.format_sql=true"
})
public class UserRepositoryTest {
@Autowired
private UserRepository userRepository;
@Test
public void test() {
User user = new User();
user.setId(1);
user.setName("test001");
userRepository.save(user);
List list = userRepository.findAll();
Assertions.assertEquals(1, list.size());
}
}
如果不想要用测试生成的h2,而是想要使用代码中配置的DataSource,可以加上@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
,这个配置的意思是不会覆盖app默认的DataSource,即如果原本配的是MySQL数据库的DataSource,那么测试的时候也会去MySQL中操作数据。
7. 使用@WebMvcTest
上述的:
@SpringBootTest
聚集的是整个Spring框架的集成测试。
@DataJpaTest
让我们更好的聚焦JPA层的测试(通过h2内嵌数据库来测试)。
而@WebMvcTest
让我们关注的是Controller层的测试。它会自动生成Spring MVC框架相关的配置。它可以让我们测试应用的Web层行为是否正确,并且不想卷入数据库调用。
以下是示例:
首先是Controller:
@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
private UserService userService;
@GetMapping
public @ResponseBody List listUser() {
return userService.list();
}
}
测试类:
@WebMvcTest
位于spring-boot-test-autoconfigure
包中,传入的Controller类相当于是测试目标类。因为它只会激活这个Controller的相关配置(API等),所以UserController中的UserService并不会被扫描到并且注入,所以需要使用@MockBean
来mock。
@MockMvc
相关的类(包括MockMvcRequestBuilders等)位于spring-test包中,是spring框架中重要的子框架之一。
-
MockMvcRequestBuilders
:提供了很重要的static方法如get(URI), post(URI), put(URI), delete(URI)等。很多人会在import的时候声明到get方法,这样就可以在代码中直接使用get(URI)了。 -
MockMvcResultMatchers
:用来匹配执行完成后的结果,其中的status(), jsonPath()也都是静态方法。
@WebMvcTest(UserController.class)
public class UserControllerTest {
@Autowired
private MockMvc mvc;
@MockBean
private UserService userService;
@Test
public void test_list() throws Exception {
List result = new ArrayList<>();
result.add(new User(1, "user01"));
result.add(new User(2, "user02"));
Mockito.when(userService.list()).thenReturn(result);
mvc.perform(MockMvcRequestBuilders.get("/users"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$[0]['id']").value(1))
.andExpect(MockMvcResultMatchers.jsonPath("$[0]['name']").value("user01"));
}
}
注:@WebMvcTest
不能与@SpringBootTest
一起用,否则就会报错:Configuration error: found multiple declarations of @BootstrapWith。
8. 其它Spring boot test auto-configure包中的注解
- @WebFluxTest
- @JdbcTest
- @JooqTest
- @DataMongoTest
- @DataRedisTest
- @DataLdapTest
- @RestClientTest
- @JsonTest
参考:
- Spring官网guide - testing-web
- baeldung - Testing in Spring Boot
- When to use @RunWith and when @ExtendWith
- junit-vintage-engine和junit-jupiter-engine之间的区别?
- Spring、Spring Boot和TestNG测试指南 - @TestConfiguration
- H2_Database 概述、下载与安装、及使用入门
- Spring Boot @DataJpaTest 教程