SpringBoot单元测试注解使用

wen java案例 2

本文目录导读:

SpringBoot单元测试注解使用

  1. 核心测试注解
  2. 辅助测试注解
  3. 配置相关注解
  4. 事务和数据库相关
  5. 实际测试示例
  6. 注解使用建议

我来详细说明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单元测试,根据测试需求选择合适的注解,可以提高测试速度和代码质量。

抱歉,评论功能暂时关闭!