Spring 框架提供了一个专门的测试模块(spring-test
),用于应用程序的集成测试。 在 Spring Boot 中,你可以通过spring-boot-starter-test
启动器快速开启和使用它。
# pom.xml
1
2
3
4
5
<dependency >
<groupId > org.springframework.boot</groupId >
<artifactId > spring-boot-starter-test</artifactId >
<scope > test</scope >
</dependency >
1. Junit 测试 当你的单元测试代码不需要用到 Spring Boot 功能,而只是一个简单的测试时,你可以直接编写你的 Junit 测试代码:
1
2
3
4
5
6
7
8
public class SimpleJunitTest {
@Test
public void testSayHi () {
System.out.println("Hi Junit." );
}
}
2. Spring Boot 测试 当你的集成测试代码需要用到 Spring Boot 功能时,你可以使用@SpringBootTest
注解。 该注解是普通的 Spring 项目(非 Spring Boot 项目)中编写集成测试代码所使用的@ContextConfiguration
注解的替代品。其作用是用于确定如何装载 Spring 应用程序的上下文资源。
1
2
3
4
5
6
7
8
9
10
11
12
13
@RunWith (SpringRunner.class)
@SpringBootTest
public class BeanInjectTest {
@Autowired
private HelloService helloService;
@Test
public void testSayHi () {
System.out.println(helloService.sayHi());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
@Service
public class HelloService {
public String sayHi () {
return "--- Hi ---" ;
}
public String sayHello () {
return "--- Hello ---" ;
}
}
当运行 Spring Boot 应用程序测试时,它会自动的从当前测试类所在的包起一层一层向上搜索,直到找到一个@SpringBootApplication
或@SpringBootConfiguration
注释类为止。以此来确定如何装载 Spring 应用程序的上下文资源。只要你以合理的方式组织你的代码,你项目的主配置通常是可以被发现的。本示例项目的部分文件结构图为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
spring-boot-testing-sample
\__ src
\__ main
: \__ java
: \__ org
: \__ fanlychie
: |__ Application.java
: \__ service
: |__ HelloService.java
\__ test
\__ java
\__ org
\__ fanlychie
\__ test
|__ BeanInjectTest.java
其中,主配置启动类的代码为:
1
2
3
4
5
6
7
8
@SpringBootApplication
public class Application {
public static void main (String[] args) {
SpringApplication.run(Application.class);
}
}
如果搜索算法搜索不到你项目的主配置文件,将报出异常:
java.lang.IllegalStateException: Unable to find a @SpringBootConfiguration, you need to use @ContextConfiguration or @SpringBootTest(classes=…) with your test
解决办法是,按 Spring Boot 的约定重新组织你的代码结构,或者手工指定你要装载的主配置文件:
1
2
3
4
5
6
7
@RunWith (SpringRunner.class)
@SpringBootTest (classes = {YourApplication.class})
public class BeanInjectTest {
}
基于 Spring 环境的 Junit 集成测试还需要使用@RunWith(SpringJUnit4ClassRunner.class)
注解,该注解能够改变 Junit 并让其运行在 Spring 的测试环境,以得到 Spring 测试环境的上下文支持。否则,在 Junit 测试中,Bean 的自动装配等注解将不起作用。但由于 SpringJUnit4ClassRunner 不方便记忆,Spring 4.3 起提供了一个等同于 SpringJUnit4ClassRunner 的类 SpringRunner,因此可以简写成:@RunWith(SpringRunner.class)
。
3. Spring MVC 测试 当你想对 Spring MVC 控制器编写单元测试代码时,可以使用@WebMvcTest
注解。它提供了自配置的 MockMvc,可以不需要完整启动 HTTP 服务器就可以快速测试 MVC 控制器。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@RunWith (SpringRunner.class)
@WebMvcTest (HelloController.class)
public class HelloControllerTest {
@Autowired
private MockMvc mvc;
@Test
public void testHello () throws Exception {
mvc.perform(get("/hello" ))
.andExpect(status().isOk())
.andDo(print());
}
}
1
2
3
4
5
6
7
8
9
10
@Controller
public class HelloController {
@GetMapping ("/hello" )
public String hello (ModelMap model) {
model.put("message" , "Hello Page" );
return "hello" ;
}
}
使用@WebMvcTest
注解时,只有一部分的 Bean 能够被扫描得到,它们分别是:
@Controller
@ControllerAdvice
@JsonComponent
Filter
WebMvcConfigurer
HandlerMethodArgumentResolver
其他常规的@Component
(包括@Service
、@Repository
等)Bean 则不会被加载到 Spring 测试环境上下文中。
如果测试的 MVC 控制器中需要@Component
Bean 的参与,你可以使用@MockBean
注解来协助完成:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import static org.mockito.BDDMockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@RunWith (SpringRunner.class)
@WebMvcTest (HelloController.class)
public class HelloControllerTest {
@Autowired
private MockMvc mvc;
@MockBean
private HelloService helloService;
@Test
public void testSayHi () throws Exception {
when(helloService.sayHi()).thenReturn("=== Hi ===" );
mvc.perform(get("/hello/sayHi" ))
.andExpect(status().isOk())
.andDo(print());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
@Controller
public class HelloController {
@Autowired
private HelloService helloService;
@GetMapping ("/hello/sayHi" )
public String sayHi (ModelMap model) {
model.put("message" , helloService.sayHi());
return "hello" ;
}
}
4. Spring Boot Web 测试 当你想启动一个完整的 HTTP 服务器对 Spring Boot 的 Web 应用编写测试代码时,可以使用@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
注解开启一个随机的可用端口。Spring Boot 针对 REST 调用的测试提供了一个 TestRestTemplate 模板,它可以解析链接服务器的相对地址。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RunWith (SpringRunner.class)
@SpringBootTest (webEnvironment = WebEnvironment.RANDOM_PORT)
public class ApplicationTest {
@Autowired
private TestRestTemplate restTemplate;
@Test
public void testSayHello () {
Map<String, Object> result = restTemplate.getForObject("/hello/sayHello" , Map.class);
System.out.println(result.get("message" ));
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Controller
public class HelloController {
@Autowired
private HelloService helloService;
@GetMapping ("/hello/sayHello" )
public @ResponseBody Object helloInfo () {
Map<String, Object> map = new HashMap<>();
map.put("message" , helloService.sayHello());
return map;
}
}
5. Spring Data JPA 测试 当你想对 Spring Data JPA 应用进行单元测试时,你可以使用@DataJpaTest
注解。并且在进行 JPA 测试时,你可以选择使用内存数据库还是真实的数据库测试。
5.1 内存数据库测试 默认情况下,@DataJpaTest
使用的是内存数据库进行测试,你无需配置和启用真实的数据库。只需要在 pom.xml 配置文件中声明如下依赖即可:
# pom.xml
1
2
3
4
<dependency >
<groupId > com.h2database</groupId >
<artifactId > h2</artifactId >
</dependency >
@DataJpaTest
注解它只扫描@Entity
Bean 和装配 Spring Data JPA 存储库,其他常规的@Component
(包括@Service
、@Repository
等)Bean 则不会被加载到 Spring 测试环境上下文。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@RunWith (SpringRunner.class)
@DataJpaTest
public class UserRepositoryInMemoryTest {
@Autowired
private UserRepository userRepository;
@Test
public void testSave () {
User user = new User();
user.setName("fanlychie" );
userRepository.save(user);
System.out.println("====================================" );
System.out.println(userRepository.findAll());
System.out.println("====================================" );
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
@Entity (name = "User" )
public class User {
@Id
@GeneratedValue (generator = "uuidGenerator" )
@GenericGenerator (name = "uuidGenerator" , strategy = "uuid" )
private String id;
private String name;
}
1
2
3
public interface UserRepository extends JpaRepository <User , String > {
}
5.2 真实数据库测试 如果你希望使用真实的数据库做测试,你可以使用@AutoConfigureTestDatabase(replace = Replace.NONE)
注解:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@RunWith (SpringRunner.class)
@DataJpaTest
@AutoConfigureTestDatabase (replace = Replace.NONE)
public class UserRepositoryMySQLTest {
@Autowired
private UserRepository userRepository;
@Test
public void testSave () {
User user = new User();
user.setName("fanlychie" );
userRepository.save(user);
System.out.println("====================================" );
System.out.println(userRepository.findAll());
System.out.println("====================================" );
}
}
replace = Replace.NONE
的作用是告知 Spring Boot 不要替换应用程序默认的数据源。
# src/main.resources/application.yml
1
2
3
4
5
6
7
8
9
10
11
spring:
datasource:
url: jdbc:mysql://127.0.0.1/test
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
tomcat:
default-auto-commit: true
jpa:
hibernate:
ddl-auto: update
5.3 事务控制 默认情况下,在每个 JPA 测试结束时,事务会发生回滚。这在一定程度上可以防止测试数据污染数据库。如果你不希望事务发生回滚,你可以使用@Rollback(false)
注解,该注解可以标注在类级别做全局的控制,也可以标注在某个特定不需要执行事务回滚的方法级别上。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@RunWith (SpringRunner.class)
@DataJpaTest
@AutoConfigureTestDatabase (replace = Replace.NONE)
public class UserRepositoryMySQLTest {
@Autowired
private UserRepository userRepository;
@Test
@Rollback (false )
public void testSave () {
User user = new User();
user.setName("fanlychie" );
userRepository.save(user);
System.out.println("====================================" );
System.out.println(userRepository.findAll());
System.out.println("====================================" );
}
}
另外,你也可以使用@Transactional
注解对事务进行控制。该注解可以标注在类级别做全局的控制,也可以标注在某个特定的方法级别上。如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@RunWith (SpringRunner.class)
@DataJpaTest
@AutoConfigureTestDatabase (replace = Replace.NONE)
public class UserRepositoryMySQLTest {
@Autowired
private UserRepository userRepository;
@Test
@Transactional (readOnly = true )
public void testSelect () {
System.out.println("====================================" );
System.out.println(userRepository.findAll());
System.out.println("====================================" );
}
}
6. 关闭 DEBUG 日志和输出 SQL 信息 在 Spring Boot 环境中执行 Junit 单元测试的时候,会有很多DEBUG
和INFO
级别的日志信息输出。我们对这些信息其实并不是很感兴趣,而是更关心自己编写的测试代码部分输出的信息以及 SQL 语句信息。正确关闭这些日志信息的姿势是,在测试目录的资源文件夹中创建一个logback-test.xml
文件:
# src/test/resources/logback-test.xml
1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="UTF-8"?>
<configuration >
<include resource ="org/springframework/boot/logging/logback/base.xml" />
<root level ="ERROR" />
<logger name ="org.hibernate.SQL" level ="DEBUG" />
<logger name ="org.hibernate.type.descriptor.sql.BasicBinder" level ="TRACE" />
</configuration >
示例项目开发环境:Java-8、Maven-3、IntelliJ IDEA-2017、Spring Boot-1.5.2.RELEASE 完整示例项目链接:spring-boot-testing-sample 参考文档文献链接:http://docs.spring.io/spring-boot/docs/1.5.2.RELEASE/reference/htmlsingle/#boot-features-testing