本文目录导读:

我来详细说明SpringBoot中常用的单元测试注解及其使用方式。
核心测试注解
@SpringBootTest
// 基础用法 - 加载完整的Spring应用上下文
@SpringBootTest
class UserServiceTest {
@Autowired
private UserService userService;
@Test
void testFindUser() {
User user = userService.findById(1L);
assertNotNull(user);
}
}
// 指定配置类和属性
@SpringBootTest(
classes = Application.class,
properties = {"spring.profiles.active=test"}
)
class ServiceTest {
// 测试代码
}
@WebMvcTest
// 测试Controller层,只加载Web相关组件
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@Test
void testGetUser() throws Exception {
// 模拟Service层返回
when(userService.findById(1L)).thenReturn(new User(1L, "张三"));
mockMvc.perform(get("/users/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("张三"));
}
}
@DataJpaTest
// 测试JPA Repository层
@DataJpaTest
@AutoConfigureTestDatabase(replace = Replace.ANY)
class UserRepositoryTest {
@Autowired
private TestEntityManager entityManager;
@Autowired
private UserRepository userRepository;
@Test
void testFindByUsername() {
// 插入测试数据
entityManager.persist(new User("testuser", "test@example.com"));
// 执行查询
User user = userRepository.findByUsername("testuser");
// 验证结果
assertEquals("test@example.com", user.getEmail());
}
}
@RestClientTest
// 测试REST客户端
@RestClientTest(UserApiClient.class)
class UserApiClientTest {
@Autowired
private MockRestServiceServer server;
@Autowired
private UserApiClient client;
@Test
void testGetUser() {
server.expect(requestTo("/api/users/1"))
.andRespond(withSuccess(
"{\"id\":1,\"name\":\"张三\"}",
MediaType.APPLICATION_JSON
));
User user = client.getUser(1L);
assertEquals("张三", user.getName());
}
}
辅助测试注解
@MockBean 和 @SpyBean
@SpringBootTest
class OrderServiceTest {
// 创建Mock,替换Spring容器中的Bean
@MockBean
private PaymentService paymentService;
// 创建Spy,保留部分真实行为
@SpyBean
private LogService logService;
@Test
void testCreateOrder() {
// Mock方法行为
when(paymentService.processPayment(any())).thenReturn(true);
// 调用被测试方法
orderService.createOrder(new OrderDTO());
// 验证Mock方法被调用
verify(paymentService).processPayment(any());
}
}
@JsonTest
// 测试JSON序列化和反序列化
@JsonTest
class UserJsonTest {
@Autowired
private JacksonTester<User> json;
@Test
void testSerialize() throws Exception {
User user = new User(1L, "张三", 25);
// 序列化测试
assertThat(json.write(user))
.isEqualToJson("user.json");
// 反序列化测试
String content = "{\"id\":1,\"name\":\"张三\",\"age\":25}";
assertThat(json.parse(content))
.isEqualTo(new User(1L, "张三", 25));
}
}
配置相关注解
@TestConfiguration
// 为测试提供特定的配置
@SpringBootTest
class ServiceTest {
// 测试专用的配置类
@TestConfiguration
static class TestConfig {
@Bean
@Primary
public DatabaseService testDatabaseService() {
return new InMemoryDatabaseService();
}
}
@Autowired
private DatabaseService databaseService;
@Test
void testWithCustomConfig() {
// 使用测试专用配置
}
}
@ActiveProfiles
// 激活特定的Spring Profile
@SpringBootTest
@ActiveProfiles("test")
class ServiceTest {
@Value("${app.test.property}")
private String testProperty;
@Test
void testWithTestProfile() {
// 使用test profile的配置
assertEquals("test-value", testProperty);
}
}
事务和数据库相关
@Transactional 和 @Rollback
@SpringBootTest
@Transactional // 默认回滚事务
class UserServiceTest {
@Autowired
private UserService userService;
@Test
@Rollback(false) // 不自动回滚
void testSaveUserWithNoRollback() {
userService.save(new User("test", "test@test.com"));
}
@Test
@Commit // 提交事务
void testSaveUserWithCommit() {
userService.save(new User("test2", "test2@test.com"));
}
}
@Sql 和 @SqlGroup
@SpringBootTest
@Sql(scripts = "/sql/init-data.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/sql/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
class UserRepositoryTest {
@Test
void testWithSqlInitData() {
// 测试数据已由@Sql初始化
}
@Test
@SqlGroup({
@Sql("/sql/special-data.sql"),
@Sql(statements = "INSERT INTO users VALUES(100, 'special', 'special@test.com')")
})
void testWithMultipleSqlSources() {
// 使用多个SQL数据源
}
}
实际测试示例
完整的Service层测试
@ExtendWith(MockitoExtension.class) // JUnit 5
// 或 @RunWith(SpringRunner.class) // JUnit 4
@SpringBootTest
class OrderServiceCompleteTest {
@Autowired
private OrderService orderService;
@MockBean
private PaymentService paymentService;
@MockBean
private StockService stockService;
@MockBean
private NotificationService notificationService;
@Test
void testCreateOrderSuccess() {
// 准备测试数据
OrderDTO orderDTO = new OrderDTO();
orderDTO.setUserId(1L);
orderDTO.setProductId(100L);
orderDTO.setQuantity(2);
// 模拟依赖服务
when(stockService.checkStock(100L, 2)).thenReturn(true);
when(paymentService.processPayment(any())).thenReturn(true);
doNothing().when(notificationService).sendOrderConfirmation(any());
// 执行测试
Order order = orderService.createOrder(orderDTO);
// 验证结果
assertNotNull(order);
assertEquals(OrderStatus.CREATED, order.getStatus());
// 验证交互
verify(stockService).checkStock(100L, 2);
verify(paymentService).processPayment(any());
verify(notificationService).sendOrderConfirmation(any());
}
@Test
void testCreateOrderInsufficientStock() {
// 模拟库存不足
when(stockService.checkStock(100L, 999)).thenReturn(false);
// 验证异常
assertThrows(InsufficientStockException.class, () -> {
orderService.createOrder(new OrderDTO());
});
// 验证支付没有被调用
verify(paymentService, never()).processPayment(any());
}
}
API集成测试
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
class UserApiIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@Test
void testCreateUserApi() throws Exception {
// 准备请求数据
UserCreateDTO createDTO = new UserCreateDTO();
createDTO.setUsername("newuser");
createDTO.setEmail("newuser@test.com");
createDTO.setPassword("password123");
// 发送POST请求
MvcResult result = mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(createDTO)))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.id").isNumber())
.andExpect(jsonPath("$.username").value("newuser"))
.andReturn();
// 解析响应
String responseBody = result.getResponse().getContentAsString();
UserResponseDTO response = objectMapper.readValue(responseBody, UserResponseDTO.class);
// 验证结果
assertNotNull(response.getId());
assertEquals("newuser", response.getUsername());
}
@Test
void testGetUserNotFound() throws Exception {
mockMvc.perform(get("/api/users/99999"))
.andExpect(status().isNotFound())
.andExpect(jsonPath("$.error").value("User not found"));
}
}
注解使用建议
选择合适的注解
- @SpringBootTest:完整的集成测试,加载所有Bean
- @WebMvcTest:Controller测试,只加载Web层
- @DataJpaTest:JPA Repository测试
- @RestClientTest:REST客户端测试
- @JsonTest:JSON序列化/反序列化测试
性能优化建议
- 使用切片测试(Slice Test)减少加载的Bean数量
- 使用
@MockBean替代真实的Bean - 使用内存数据库(H2)进行数据库测试
- 合理使用
@DirtiesContext控制上下文刷新
这些注解的组合使用可以构建高效、可维护的SpringBoot单元测试,根据测试需求选择合适的注解,可以提高测试速度和代码质量。