Spring Boot 测试
1. 测试概述
1.1 测试类型
| 类型 | 说明 | 范围 |
|---|---|---|
| 单元测试 | 测试单个方法或类 | 最小范围 |
| 集成测试 | 测试多个组件协作 | 中等范围 |
| 端到端测试 | 测试完整业务流程 | 最大范围 |
1.2 测试依赖
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>2. 单元测试
2.1 JUnit 5 基础
java
@DisplayName("计算器测试")
class CalculatorTest {
private Calculator calculator;
@BeforeEach
void setUp() {
calculator = new Calculator();
}
@Test
@DisplayName("加法测试")
void testAdd() {
int result = calculator.add(2, 3);
assertEquals(5, result);
}
@Test
@DisplayName("除法异常测试")
void testDivideByZero() {
assertThrows(ArithmeticException.class, () -> {
calculator.divide(10, 0);
});
}
@ParameterizedTest
@ValueSource(ints = {1, 2, 3, 4, 5})
@DisplayName("参数化测试")
void testIsPositive(int number) {
assertTrue(calculator.isPositive(number));
}
@Nested
@DisplayName("嵌套测试")
class NestedTests {
@Test
void testNested() {
assertTrue(true);
}
}
}2.2 Mockito Mock
java
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@Test
void testFindById() {
User user = new User(1L, "张三", "zhangsan@example.com");
when(userRepository.findById(1L)).thenReturn(Optional.of(user));
User result = userService.findById(1L);
assertNotNull(result);
assertEquals("张三", result.getUsername());
verify(userRepository, times(1)).findById(1L);
}
@Test
void testCreate() {
User user = new User(null, "李四", "lisi@example.com");
User savedUser = new User(1L, "李四", "lisi@example.com");
when(userRepository.save(any(User.class))).thenReturn(savedUser);
User result = userService.create(user);
assertNotNull(result.getId());
verify(userRepository).save(user);
}
@Test
void testDelete() {
doNothing().when(userRepository).deleteById(1L);
userService.delete(1L);
verify(userRepository).deleteById(1L);
}
}2.3 AssertJ 断言
java
import static org.assertj.core.api.Assertions.*;
class UserTest {
@Test
void testUserAssertions() {
User user = new User(1L, "张三", "zhangsan@example.com");
assertThat(user)
.isNotNull()
.hasFieldOrProperty("id")
.hasFieldOrPropertyWithValue("username", "张三");
assertThat(user.getUsername())
.isNotEmpty()
.startsWith("张")
.endsWith("三")
.hasSize(2);
assertThat(user.getEmail())
.contains("@")
.endsWith(".com");
}
@Test
void testListAssertions() {
List<User> users = List.of(
new User(1L, "张三", "zhangsan@example.com"),
new User(2L, "李四", "lisi@example.com")
);
assertThat(users)
.hasSize(2)
.extracting("username")
.containsExactly("张三", "李四");
}
}3. 集成测试
3.1 @SpringBootTest
java
@SpringBootTest
class UserServiceIntegrationTest {
@Autowired
private UserService userService;
@Autowired
private UserRepository userRepository;
@BeforeEach
void setUp() {
userRepository.deleteAll();
}
@Test
void testCreateAndFind() {
User user = new User(null, "张三", "zhangsan@example.com");
User saved = userService.create(user);
assertThat(saved.getId()).isNotNull();
User found = userService.findById(saved.getId());
assertThat(found).isNotNull();
assertThat(found.getUsername()).isEqualTo("张三");
}
}3.2 测试配置
yaml
# src/test/resources/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: truejava
@SpringBootTest
@ActiveProfiles("test")
class UserServiceTest {
// 使用 test profile 配置
}3.3 测试数据库
java
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class UserRepositoryTest {
@Autowired
private TestEntityManager entityManager;
@Autowired
private UserRepository userRepository;
@Test
void testFindByUsername() {
User user = new User(null, "张三", "zhangsan@example.com");
entityManager.persistAndFlush(user);
Optional<User> found = userRepository.findByUsername("张三");
assertThat(found).isPresent();
assertThat(found.get().getEmail()).isEqualTo("zhangsan@example.com");
}
@Test
void testExistsByUsername() {
User user = new User(null, "张三", "zhangsan@example.com");
entityManager.persistAndFlush(user);
boolean exists = userRepository.existsByUsername("张三");
assertThat(exists).isTrue();
}
}4. Web 层测试
4.1 @WebMvcTest
java
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@Test
void testGetById() throws Exception {
User user = new User(1L, "张三", "zhangsan@example.com");
when(userService.findById(1L)).thenReturn(user);
mockMvc.perform(get("/api/users/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(1))
.andExpect(jsonPath("$.username").value("张三"))
.andExpect(jsonPath("$.email").value("zhangsan@example.com"));
}
@Test
void testCreate() throws Exception {
User user = new User(1L, "张三", "zhangsan@example.com");
String userJson = """
{
"username": "张三",
"email": "zhangsan@example.com"
}
""";
when(userService.create(any(User.class))).thenReturn(user);
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(userJson))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.username").value("张三"));
}
@Test
void testGetAll() throws Exception {
List<User> users = List.of(
new User(1L, "张三", "zhangsan@example.com"),
new User(2L, "李四", "lisi@example.com")
);
when(userService.findAll()).thenReturn(users);
mockMvc.perform(get("/api/users"))
.andExpect(status().isOk())
.andExpect(jsonPath("$").isArray())
.andExpect(jsonPath("$.length()").value(2))
.andExpect(jsonPath("$[0].username").value("张三"));
}
}4.2 @AutoConfigureMockMvc
java
@SpringBootTest
@AutoConfigureMockMvc
class UserControllerIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private UserRepository userRepository;
@BeforeEach
void setUp() {
userRepository.deleteAll();
}
@Test
void testCreateAndGet() throws Exception {
String userJson = """
{
"username": "张三",
"email": "zhangsan@example.com"
}
""";
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(userJson))
.andExpect(status().isCreated());
mockMvc.perform(get("/api/users"))
.andExpect(status().isOk())
.andExpect(jsonPath("$[0].username").value("张三"));
}
}4.3 安全测试
java
@SpringBootTest
@AutoConfigureMockMvc
class SecurityTest {
@Autowired
private MockMvc mockMvc;
@Test
@WithMockUser(username = "admin", roles = {"ADMIN"})
void testAdminEndpoint() throws Exception {
mockMvc.perform(get("/api/admin/users"))
.andExpect(status().isOk());
}
@Test
@WithMockUser(username = "user", roles = {"USER"})
void testAdminEndpointForbidden() throws Exception {
mockMvc.perform(get("/api/admin/users"))
.andExpect(status().isForbidden());
}
@Test
void testUnauthorized() throws Exception {
mockMvc.perform(get("/api/users"))
.andExpect(status().isUnauthorized());
}
@Test
@WithUserDetails("admin")
void testWithUserDetails() throws Exception {
mockMvc.perform(get("/api/profile"))
.andExpect(status().isOk());
}
}5. 测试事务
5.1 @Transactional
java
@SpringBootTest
@Transactional
class TransactionalTest {
@Autowired
private UserRepository userRepository;
@Test
void testCreate() {
User user = new User(null, "张三", "zhangsan@example.com");
userRepository.save(user);
assertThat(user.getId()).isNotNull();
}
@Test
void testRollback() {
long count = userRepository.count();
User user = new User(null, "张三", "zhangsan@example.com");
userRepository.save(user);
assertThat(userRepository.count()).isEqualTo(count + 1);
}
}5.2 @Commit 和 @Rollback
java
@SpringBootTest
class CommitRollbackTest {
@Test
@Transactional
@Rollback
void testRollback() {
// 测试后回滚
}
@Test
@Transactional
@Commit
void testCommit() {
// 测试后提交
}
}6. 测试切片
6.1 @DataJpaTest
java
@DataJpaTest
class JpaSliceTest {
@Autowired
private UserRepository userRepository;
@Test
void testRepository() {
User user = new User(null, "张三", "zhangsan@example.com");
userRepository.save(user);
assertThat(userRepository.count()).isEqualTo(1);
}
}6.2 @DataRedisTest
java
@DataRedisTest
class RedisSliceTest {
@Autowired
private StringRedisTemplate redisTemplate;
@Test
void testRedis() {
redisTemplate.opsForValue().set("test", "value");
assertThat(redisTemplate.opsForValue().get("test")).isEqualTo("value");
}
}6.3 @JsonTest
java
@JsonTest
class JsonTest {
@Autowired
private JacksonTester<User> jsonTester;
@Test
void testSerialize() throws IOException {
User user = new User(1L, "张三", "zhangsan@example.com");
JsonContent<User> result = jsonTester.write(user);
assertThat(result).hasJsonPathStringValue("$.username");
assertThat(result).extractingJsonPathStringValue("$.username").isEqualTo("张三");
}
@Test
void testDeserialize() throws IOException {
String json = """
{
"id": 1,
"username": "张三",
"email": "zhangsan@example.com"
}
""";
User result = jsonTester.parseObject(json);
assertThat(result.getUsername()).isEqualTo("张三");
}
}7. 测试工具
7.1 TestRestTemplate
java
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class RestTemplateTest {
@Autowired
private TestRestTemplate restTemplate;
@LocalServerPort
private int port;
@Test
void testGetUser() {
ResponseEntity<User> response = restTemplate.getForEntity(
"/api/users/1", User.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody()).isNotNull();
}
@Test
void testCreateUser() {
User user = new User(null, "张三", "zhangsan@example.com");
ResponseEntity<User> response = restTemplate.postForEntity(
"/api/users", user, User.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED);
}
}7.2 WebTestClient
java
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class WebTestClientTest {
@Autowired
private WebTestClient webTestClient;
@Test
void testGetUser() {
webTestClient.get()
.uri("/api/users/1")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBody(User.class)
.value(user -> assertThat(user.getUsername()).isEqualTo("张三"));
}
@Test
void testCreateUser() {
User user = new User(null, "张三", "zhangsan@example.com");
webTestClient.post()
.uri("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(user)
.exchange()
.expectStatus().isCreated()
.expectBody()
.jsonPath("$.username").isEqualTo("张三");
}
}8. 测试最佳实践
8.1 测试命名
java
class UserServiceTest {
@Test
void findById_WhenUserExists_ReturnsUser() {
// Given
User user = new User(1L, "张三", "zhangsan@example.com");
when(userRepository.findById(1L)).thenReturn(Optional.of(user));
// When
User result = userService.findById(1L);
// Then
assertThat(result).isNotNull();
assertThat(result.getUsername()).isEqualTo("张三");
}
@Test
void findById_WhenUserNotExists_ReturnsNull() {
// Given
when(userRepository.findById(1L)).thenReturn(Optional.empty());
// When
User result = userService.findById(1L);
// Then
assertThat(result).isNull();
}
}8.2 测试数据
java
class TestDataFactory {
public static User createUser() {
return new User(null, "张三", "zhangsan@example.com");
}
public static User createUser(String username) {
return new User(null, username, username.toLowerCase() + "@example.com");
}
public static List<User> createUsers(int count) {
return IntStream.range(0, count)
.mapToObj(i -> createUser("user" + i))
.toList();
}
}
class UserServiceTest {
@Test
void testCreate() {
User user = TestDataFactory.createUser();
when(userRepository.save(any())).thenReturn(user);
User result = userService.create(user);
assertThat(result).isNotNull();
}
}9. 小结
本章学习了 Spring Boot 测试的核心内容:
| 内容 | 要点 |
|---|---|
| 单元测试 | JUnit 5、Mockito、AssertJ |
| 集成测试 | @SpringBootTest、@DataJpaTest |
| Web 测试 | MockMvc、WebTestClient |
| 安全测试 | @WithMockUser、@WithUserDetails |
| 测试事务 | @Transactional、@Rollback |
| 测试切片 | @DataJpaTest、@JsonTest |
| 测试工具 | TestRestTemplate、WebTestClient |
下一章将学习 Spring Boot 可观测性。