文章目录
-
- [1. 测试框架概述](#1. 测试框架概述)
-
- [1.1 测试类型](#1.1 测试类型)
- [1.2 核心依赖](#1.2 核心依赖)
- [2. 单元测试](#2. 单元测试)
-
- [2.1 服务层测试](#2.1 服务层测试)
- [2.2 工具类测试](#2.2 工具类测试)
- [3. 集成测试](#3. 集成测试)
-
- [3.1 数据访问层测试](#3.1 数据访问层测试)
- [3.2 服务层集成测试](#3.2 服务层集成测试)
- [4. Web层测试](#4. Web层测试)
-
- [4.1 控制器测试](#4.1 控制器测试)
- [4.2 完整Web测试](#4.2 完整Web测试)
- [5. 安全测试](#5. 安全测试)
-
- [5.1 认证测试](#5.1 认证测试)
- [5.2 JWT测试](#5.2 JWT测试)
- [6. 测试配置](#6. 测试配置)
-
- [6.1 测试配置文件](#6.1 测试配置文件)
- [6.2 测试配置类](#6.2 测试配置类)
- [7. 测试最佳实践](#7. 测试最佳实践)
-
- [7.1 测试数据管理](#7.1 测试数据管理)
- [7.2 测试基类](#7.2 测试基类)
- [8. 总结](#8. 总结)
1. 测试框架概述
Spring Boot提供了完整的测试解决方案,支持单元测试、集成测试、Web测试等多种测试类型。通过Spring Boot Test模块,可以快速构建可靠的测试套件。
1.1 测试类型
- 单元测试:测试单个组件或方法
- 集成测试:测试多个组件协作
- Web测试:测试Web层功能
- 数据访问测试:测试数据访问层
- 安全测试:测试安全功能
1.2 核心依赖
xml
<dependencies>
<!-- Spring Boot Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- TestContainers -->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<!-- Mockito -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
2. 单元测试
2.1 服务层测试
java
package com.example.demo.service;
import com.example.demo.entity.User;
import com.example.demo.repository.UserRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
private User testUser;
@BeforeEach
void setUp() {
testUser = new User();
testUser.setId(1L);
testUser.setUsername("testuser");
testUser.setEmail("test@example.com");
testUser.setPassword("password");
}
@Test
void testCreateUser() {
// Given
when(userRepository.save(any(User.class))).thenReturn(testUser);
// When
User result = userService.createUser(testUser);
// Then
assertNotNull(result);
assertEquals("testuser", result.getUsername());
assertEquals("test@example.com", result.getEmail());
verify(userRepository, times(1)).save(testUser);
}
@Test
void testGetUserById() {
// Given
when(userRepository.findById(1L)).thenReturn(Optional.of(testUser));
// When
Optional<User> result = userService.getUserById(1L);
// Then
assertTrue(result.isPresent());
assertEquals("testuser", result.get().getUsername());
verify(userRepository, times(1)).findById(1L);
}
@Test
void testGetUserByIdNotFound() {
// Given
when(userRepository.findById(999L)).thenReturn(Optional.empty());
// When
Optional<User> result = userService.getUserById(999L);
// Then
assertFalse(result.isPresent());
verify(userRepository, times(1)).findById(999L);
}
@Test
void testUpdateUser() {
// Given
User updatedUser = new User();
updatedUser.setId(1L);
updatedUser.setUsername("updateduser");
updatedUser.setEmail("updated@example.com");
when(userRepository.findById(1L)).thenReturn(Optional.of(testUser));
when(userRepository.save(any(User.class))).thenReturn(updatedUser);
// When
User result = userService.updateUser(1L, updatedUser);
// Then
assertNotNull(result);
assertEquals("updateduser", result.getUsername());
verify(userRepository, times(1)).findById(1L);
verify(userRepository, times(1)).save(any(User.class));
}
@Test
void testDeleteUser() {
// Given
when(userRepository.existsById(1L)).thenReturn(true);
// When
userService.deleteUser(1L);
// Then
verify(userRepository, times(1)).existsById(1L);
verify(userRepository, times(1)).deleteById(1L);
}
}
2.2 工具类测试
java
package com.example.demo.util;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static org.junit.jupiter.api.Assertions.*;
class StringUtilsTest {
@Test
void testIsEmpty() {
assertTrue(StringUtils.isEmpty(null));
assertTrue(StringUtils.isEmpty(""));
assertFalse(StringUtils.isEmpty("hello"));
}
@Test
void testIsBlank() {
assertTrue(StringUtils.isBlank(null));
assertTrue(StringUtils.isBlank(""));
assertTrue(StringUtils.isBlank(" "));
assertFalse(StringUtils.isBlank("hello"));
}
@ParameterizedTest
@ValueSource(strings = {"hello", "world", "test"})
void testIsNotEmpty(String input) {
assertTrue(StringUtils.isNotEmpty(input));
}
@Test
void testTrim() {
assertEquals("hello", StringUtils.trim(" hello "));
assertEquals("", StringUtils.trim(" "));
assertNull(StringUtils.trim(null));
}
}
3. 集成测试
3.1 数据访问层测试
java
package com.example.demo.repository;
import com.example.demo.entity.User;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import org.springframework.test.context.ActiveProfiles;
import java.util.List;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.*;
@DataJpaTest
@ActiveProfiles("test")
class UserRepositoryTest {
@Autowired
private TestEntityManager entityManager;
@Autowired
private UserRepository userRepository;
private User testUser;
@BeforeEach
void setUp() {
testUser = new User();
testUser.setUsername("testuser");
testUser.setEmail("test@example.com");
testUser.setPassword("password");
testUser.setFullName("Test User");
}
@Test
void testFindByUsername() {
// Given
entityManager.persistAndFlush(testUser);
// When
Optional<User> result = userRepository.findByUsername("testuser");
// Then
assertTrue(result.isPresent());
assertEquals("testuser", result.get().getUsername());
}
@Test
void testFindByEmail() {
// Given
entityManager.persistAndFlush(testUser);
// When
Optional<User> result = userRepository.findByEmail("test@example.com");
// Then
assertTrue(result.isPresent());
assertEquals("test@example.com", result.get().getEmail());
}
@Test
void testFindByStatus() {
// Given
testUser.setStatus(User.UserStatus.ACTIVE);
entityManager.persistAndFlush(testUser);
// When
List<User> result = userRepository.findByStatus(User.UserStatus.ACTIVE);
// Then
assertEquals(1, result.size());
assertEquals(User.UserStatus.ACTIVE, result.get(0).getStatus());
}
@Test
void testCustomQuery() {
// Given
entityManager.persistAndFlush(testUser);
// When
Long count = userRepository.countActiveUsers();
// Then
assertTrue(count >= 0);
}
}
3.2 服务层集成测试
java
package com.example.demo.service;
import com.example.demo.entity.User;
import com.example.demo.repository.UserRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
@ActiveProfiles("test")
@Transactional
class UserServiceIntegrationTest {
@Autowired
private UserService userService;
@Autowired
private UserRepository userRepository;
private User testUser;
@BeforeEach
void setUp() {
testUser = new User();
testUser.setUsername("testuser");
testUser.setEmail("test@example.com");
testUser.setPassword("password");
testUser.setFullName("Test User");
}
@Test
void testCreateAndRetrieveUser() {
// When
User createdUser = userService.createUser(testUser);
// Then
assertNotNull(createdUser.getId());
assertEquals("testuser", createdUser.getUsername());
// When
Optional<User> retrievedUser = userService.getUserById(createdUser.getId());
// Then
assertTrue(retrievedUser.isPresent());
assertEquals("testuser", retrievedUser.get().getUsername());
}
@Test
void testUpdateUser() {
// Given
User createdUser = userService.createUser(testUser);
// When
User updatedUser = new User();
updatedUser.setFullName("Updated User");
updatedUser.setEmail("updated@example.com");
User result = userService.updateUser(createdUser.getId(), updatedUser);
// Then
assertEquals("Updated User", result.getFullName());
assertEquals("updated@example.com", result.getEmail());
}
@Test
void testDeleteUser() {
// Given
User createdUser = userService.createUser(testUser);
// When
userService.deleteUser(createdUser.getId());
// Then
Optional<User> deletedUser = userService.getUserById(createdUser.getId());
assertFalse(deletedUser.isPresent());
}
}
4. Web层测试
4.1 控制器测试
java
package com.example.demo.controller;
import com.example.demo.entity.User;
import com.example.demo.service.UserService;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import java.util.Optional;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@Autowired
private ObjectMapper objectMapper;
private User testUser;
@BeforeEach
void setUp() {
testUser = new User();
testUser.setId(1L);
testUser.setUsername("testuser");
testUser.setEmail("test@example.com");
testUser.setFullName("Test User");
}
@Test
void testGetUserById() throws Exception {
// Given
when(userService.getUserById(1L)).thenReturn(Optional.of(testUser));
// When & Then
mockMvc.perform(get("/api/users/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(1))
.andExpect(jsonPath("$.username").value("testuser"))
.andExpect(jsonPath("$.email").value("test@example.com"));
}
@Test
void testGetUserByIdNotFound() throws Exception {
// Given
when(userService.getUserById(999L)).thenReturn(Optional.empty());
// When & Then
mockMvc.perform(get("/api/users/999"))
.andExpect(status().isNotFound());
}
@Test
void testCreateUser() throws Exception {
// Given
when(userService.createUser(any(User.class))).thenReturn(testUser);
// When & Then
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(testUser)))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.username").value("testuser"));
}
@Test
void testUpdateUser() throws Exception {
// Given
User updatedUser = new User();
updatedUser.setFullName("Updated User");
updatedUser.setEmail("updated@example.com");
when(userService.updateUser(1L, any(User.class))).thenReturn(updatedUser);
// When & Then
mockMvc.perform(put("/api/users/1")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(updatedUser)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.fullName").value("Updated User"));
}
@Test
void testDeleteUser() throws Exception {
// When & Then
mockMvc.perform(delete("/api/users/1"))
.andExpect(status().isNoContent());
}
}
4.2 完整Web测试
java
package com.example.demo.controller;
import com.example.demo.entity.User;
import com.example.demo.repository.UserRepository;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureWebMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.transaction.annotation.Transactional;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@SpringBootTest
@AutoConfigureWebMvc
@ActiveProfiles("test")
@Transactional
class UserControllerIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private UserRepository userRepository;
@Autowired
private ObjectMapper objectMapper;
private User testUser;
@BeforeEach
void setUp() {
testUser = new User();
testUser.setUsername("testuser");
testUser.setEmail("test@example.com");
testUser.setPassword("password");
testUser.setFullName("Test User");
}
@Test
void testCreateAndRetrieveUser() throws Exception {
// When
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(testUser)))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.username").value("testuser"));
// Then
User savedUser = userRepository.findByUsername("testuser").orElse(null);
assertNotNull(savedUser);
// When
mockMvc.perform(get("/api/users/" + savedUser.getId()))
.andExpect(status().isOk())
.andExpect(jsonPath("$.username").value("testuser"));
}
@Test
void testUpdateUser() throws Exception {
// Given
User savedUser = userRepository.save(testUser);
// When
User updatedUser = new User();
updatedUser.setFullName("Updated User");
updatedUser.setEmail("updated@example.com");
mockMvc.perform(put("/api/users/" + savedUser.getId())
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(updatedUser)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.fullName").value("Updated User"));
}
@Test
void testDeleteUser() throws Exception {
// Given
User savedUser = userRepository.save(testUser);
// When
mockMvc.perform(delete("/api/users/" + savedUser.getId()))
.andExpect(status().isNoContent());
// Then
assertFalse(userRepository.existsById(savedUser.getId()));
}
}
5. 安全测试
5.1 认证测试
java
package com.example.demo.security;
import com.example.demo.entity.User;
import com.example.demo.repository.UserRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureWebMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.transaction.annotation.Transactional;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@SpringBootTest
@AutoConfigureWebMvc
@ActiveProfiles("test")
@Transactional
class SecurityTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private UserRepository userRepository;
private User testUser;
@BeforeEach
void setUp() {
testUser = new User();
testUser.setUsername("testuser");
testUser.setEmail("test@example.com");
testUser.setPassword("password");
testUser.setEnabled(true);
userRepository.save(testUser);
}
@Test
void testUnauthenticatedAccess() throws Exception {
mockMvc.perform(get("/api/users"))
.andExpect(status().isUnauthorized());
}
@Test
@WithMockUser(username = "testuser", roles = "USER")
void testAuthenticatedAccess() throws Exception {
mockMvc.perform(get("/api/users"))
.andExpect(status().isOk());
}
@Test
@WithMockUser(username = "admin", roles = "ADMIN")
void testAdminAccess() throws Exception {
mockMvc.perform(get("/api/admin/users"))
.andExpect(status().isOk());
}
@Test
@WithMockUser(username = "user", roles = "USER")
void testUserCannotAccessAdmin() throws Exception {
mockMvc.perform(get("/api/admin/users"))
.andExpect(status().isForbidden());
}
}
5.2 JWT测试
java
package com.example.demo.security;
import com.example.demo.util.JwtTokenUtil;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureWebMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
import java.util.ArrayList;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@SpringBootTest
@AutoConfigureWebMvc
@ActiveProfiles("test")
class JwtSecurityTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private JwtTokenUtil jwtTokenUtil;
private String validToken;
@BeforeEach
void setUp() {
UserDetails userDetails = new User("testuser", "password", new ArrayList<>());
validToken = jwtTokenUtil.generateToken(userDetails);
}
@Test
void testValidJwtToken() throws Exception {
mockMvc.perform(get("/api/users")
.header("Authorization", "Bearer " + validToken))
.andExpect(status().isOk());
}
@Test
void testInvalidJwtToken() throws Exception {
mockMvc.perform(get("/api/users")
.header("Authorization", "Bearer invalid-token"))
.andExpect(status().isUnauthorized());
}
@Test
void testMissingJwtToken() throws Exception {
mockMvc.perform(get("/api/users"))
.andExpect(status().isUnauthorized());
}
}
6. 测试配置
6.1 测试配置文件
yaml
# application-test.yml
spring:
datasource:
url: jdbc:h2:mem:testdb
driver-class-name: org.h2.Driver
username: sa
password:
jpa:
hibernate:
ddl-auto: create-drop
show-sql: true
properties:
hibernate:
format_sql: true
security:
user:
name: test
password: test
roles: USER
logging:
level:
com.example.demo: DEBUG
org.springframework.security: DEBUG
6.2 测试配置类
java
package com.example.demo.config;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
@TestConfiguration
public class TestSecurityConfig {
@Bean
@Primary
public PasswordEncoder testPasswordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
}
7. 测试最佳实践
7.1 测试数据管理
java
package com.example.demo.util;
import com.example.demo.entity.User;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
@Component
public class TestDataFactory {
public static User createTestUser() {
User user = new User();
user.setUsername("testuser");
user.setEmail("test@example.com");
user.setPassword("password");
user.setFullName("Test User");
user.setEnabled(true);
user.setCreatedAt(LocalDateTime.now());
return user;
}
public static User createTestUser(String username, String email) {
User user = createTestUser();
user.setUsername(username);
user.setEmail(email);
return user;
}
public static User createAdminUser() {
User user = createTestUser();
user.setUsername("admin");
user.setEmail("admin@example.com");
user.setFullName("Admin User");
return user;
}
}
7.2 测试基类
java
package com.example.demo.base;
import org.junit.jupiter.api.BeforeEach;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureWebMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.transaction.annotation.Transactional;
@SpringBootTest
@AutoConfigureWebMvc
@ActiveProfiles("test")
@Transactional
public abstract class BaseIntegrationTest {
@Autowired
protected MockMvc mockMvc;
@BeforeEach
void setUp() {
// 通用测试设置
}
}
8. 总结
Spring Boot测试框架提供了完整的测试解决方案:
- 单元测试:使用Mockito进行组件隔离测试
- 集成测试:测试组件协作和数据访问
- Web测试:使用MockMvc测试Web层功能
- 安全测试:测试认证和授权功能
- 测试配置:灵活的测试环境配置
- 最佳实践:测试数据管理和测试基类
通过合理使用这些测试技术,可以构建出可靠的测试套件,确保应用质量。
下一篇:Spring Boot微服务架构详解