Spring Test 从入门到实战
摘要:本文将带你从零开始,循序渐进地学习Spring Test框架。你将掌握单元测试、集成测试、Mock测试等核心技能!

Spring Test 概述
Spring Test是Spring框架提供的测试模块,它简化了Spring应用程序的测试过程。通过Spring Test,我们可以:
- 快速编写测试:无需手动创建Spring上下文
- 依赖注入:自动注入测试所需的Bean
- 事务管理:支持测试中的事务回滚
- 集成测试:轻松进行Web层、Service层的集成测试

核心优势
- 简化配置:通过注解自动配置测试环境
- 上下文缓存:提高测试执行效率
- 丰富的断言:提供多种断言方法
- Mock支持:与Mockito完美集成
环境搭建
Maven依赖配置
xml
<dependencies>
<!-- Spring Boot Test Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- JUnit 5 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<!-- Mockito -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<!-- AssertJ -->
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
核心注解详解
1. @SpringBootTest
java
@SpringBootTest
class UserServiceTest {
@Autowired
private UserService userService;
@Test
void testFindUserById() {
User user = userService.findUserById(1L);
assertThat(user).isNotNull();
assertThat(user.getId()).isEqualTo(1L);
}
}
2. @TestConfiguration
java
@TestConfiguration
class TestConfig {
@Bean
public UserService userService() {
return new UserService();
}
}
3. @AutoConfigureMockMvc
java
@SpringBootTest
@AutoConfigureMockMvc
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
void testGetUser() throws Exception {
mockMvc.perform(get("/api/users/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(1));
}
}
测试生命周期

生命周期方法
java
@SpringBootTest
class LifecycleTest {
@BeforeAll
static void setup() {
// 在所有测试方法执行前运行一次
System.out.println("BeforeAll: 初始化测试环境");
}
@BeforeEach
void beforeEach() {
// 在每个测试方法执行前运行
System.out.println("BeforeEach: 准备测试数据");
}
@Test
void test1() {
System.out.println("Test1: 执行测试");
}
@Test
void test2() {
System.out.println("Test2: 执行测试");
}
@AfterEach
void afterEach() {
// 在每个测试方法执行后运行
System.out.println("AfterEach: 清理测试数据");
}
@AfterAll
static void tearDown() {
// 在所有测试方法执行后运行一次
System.out.println("AfterAll: 清理测试环境");
}
}
实战案例
案例1:用户服务单元测试
java
@SpringBootTest
class UserServiceTest {
@Autowired
private UserService userService;
@Test
void testCreateUser() {
// Given
UserDTO userDTO = new UserDTO("张三", "zhangsan@example.com");
// When
User user = userService.createUser(userDTO);
// Then
assertThat(user).isNotNull();
assertThat(user.getName()).isEqualTo("张三");
assertThat(user.getEmail()).isEqualTo("zhangsan@example.com");
assertThat(user.getId()).isNotNull();
}
@Test
void testCreateUserWithExistingEmail() {
// Given
UserDTO userDTO = new UserDTO("李四", "zhangsan@example.com");
// When & Then
assertThrows(UserAlreadyExistsException.class, () -> {
userService.createUser(userDTO);
});
}
}
案例2:REST API集成测试
java
@SpringBootTest
@AutoConfigureMockMvc
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
void testGetUserById() throws Exception {
// When & Then
mockMvc.perform(get("/api/users/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(1))
.andExpect(jsonPath("$.name").value("张三"));
}
@Test
void testCreateUser() throws Exception {
// Given
String userJson = """
{
"name": "王五",
"email": "wangwu@example.com"
}
""";
// When & Then
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(userJson))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.name").value("王五"));
}
}
案例3:使用Mock进行测试
java
@SpringBootTest
class UserServiceWithMockTest {
@MockBean
private UserRepository userRepository;
@Autowired
private UserService userService;
@Test
void testFindUserById_NotFound() {
// Given
when(userRepository.findById(1L)).thenReturn(Optional.empty());
// When & Then
assertThrows(UserNotFoundException.class, () -> {
userService.findUserById(1L);
});
}
@Test
void testUpdateUser_Success() {
// Given
User existingUser = new User(1L, "张三", "zhangsan@example.com");
when(userRepository.findById(1L)).thenReturn(Optional.of(existingUser));
when(userRepository.save(any(User.class))).thenReturn(existingUser);
UserDTO updateDTO = new UserDTO("张三三", "zhangsan3@example.com");
// When
User updatedUser = userService.updateUser(1L, updateDTO);
// Then
assertThat(updatedUser.getEmail()).isEqualTo("zhangsan3@example.com");
verify(userRepository).save(any(User.class));
}
}
案例4:事务测试
java
@SpringBootTest
@Transactional
class TransactionalUserServiceTest {
@Autowired
private UserService userService;
@Autowired
private UserRepository userRepository;
@Test
void testCreateUser_WithTransaction() {
// Given
UserDTO userDTO = new UserDTO("赵六", "zhaoliu@example.com");
// When
User user = userService.createUser(userDTO);
// Then
assertThat(user).isNotNull();
assertThat(userRepository.count()).isEqualTo(1);
}
@Test
void testCreateUser_WithoutTransaction() {
// Given
UserDTO userDTO = new UserDTO("钱七", "qianqi@example.com");
// When
User user = userService.createUserWithoutTransaction(userDTO);
// Then
assertThat(user).isNotNull();
// 数据会被回滚
assertThat(userRepository.count()).isEqualTo(0);
}
}
高级特性
1. 参数化测试
java
@ParameterizedTest
@ValueSource(strings = {"test1@example.com", "test2@example.com", "test3@example.com"})
void testEmailValidation(String email) {
assertThat(EmailValidator.isValidEmail(email)).isTrue();
}
@ParameterizedTest
@MethodSource("provideUserDTOs")
void testCreateUserWithDifferentData(UserDTO userDTO) {
User user = userService.createUser(userDTO);
assertThat(user).isNotNull();
assertThat(user.getEmail()).isEqualTo(userDTO.getEmail());
}
private static Stream<Arguments> provideUserDTOs() {
return Stream.of(
Arguments.of(new UserDTO("用户1", "user1@example.com")),
Arguments.of(new UserDTO("用户2", "user2@example.com")),
Arguments.of(new UserDTO("用户3", "user3@example.com"))
);
}
2. 性能测试
java
@SpringBootTest
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public class PerformanceTest {
@Autowired
private UserService userService;
@Test
@Benchmark
void testUserServicePerformance() {
// 测试性能
for (int i = 0; i < 1000; i++) {
userService.findUserById((long) i);
}
}
}
测试覆盖率

如何提高测试覆盖率
- 单元测试:覆盖所有业务逻辑分支
- 边界测试:测试边界条件和异常情况
- 集成测试:验证组件间的交互
- 端到端测试:验证完整的业务流程
覆盖率工具
java
// 使用JaCoCo进行覆盖率分析
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.7</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
最佳实践
1. 测试命名规范
java
// 好的命名
@Test
void shouldCreateUserWhenValidDataProvided() {}
// 不好的命名
@Test
void test1() {}
2. 测试结构
java
@Test
void testFeature() {
// 1. Arrange (准备测试数据)
UserDTO userDTO = new UserDTO("张三", "zhangsan@example.com");
// 2. Act (执行测试操作)
User user = userService.createUser(userDTO);
// 3. Assert (验证结果)
assertThat(user).isNotNull();
assertThat(user.getName()).isEqualTo("张三");
}
3. 异步测试
java
@Test
void testAsyncOperation() throws InterruptedException, ExecutionException {
// Given
CompletableFuture<User> future = userService.asyncCreateUser(
new UserDTO("李四", "lisi@example.com"));
// When
User user = future.get();
// Then
assertThat(user).isNotNull();
assertThat(user.getName()).isEqualTo("李四");
}
依赖关系

总结
Spring Test是Java开发中不可或缺的工具,掌握它将大大提高你的开发效率和代码质量。