Spring Boot Web 服务单元测试设计指南

在 Spring Boot Web 项目中,单元测试应聚焦单个组件的验证,隔离外部依赖(如数据库、网络服务)。以下是分层测试策略和最佳实践:

一、核心测试框架组合
XML 复制代码
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-core</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
二、分层单元测试策略

Service 层测试

使用 Mockito 模拟 Repository 依赖

java 复制代码
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
    
    @Mock
    private UserRepository userRepository;
    
    @InjectMocks
    private UserService userService;

    @Test
    void getUserById_WhenExists_ReturnsUser() {
        // 1. 准备 Mock 数据
        User mockUser = new User(1L, "test@example.com");
        when(userRepository.findById(1L)).thenReturn(Optional.of(mockUser));

        // 2. 执行测试
        User result = userService.getUserById(1L);

        // 3. 验证结果
        assertThat(result.getEmail()).isEqualTo("test@example.com");
        verify(userRepository).findById(1L); // 验证调用
    }

    @Test
    void getUserById_WhenNotExists_ThrowsException() {
        when(userRepository.findById(anyLong())).thenReturn(Optional.empty());
        
        assertThatThrownBy(() -> userService.getUserById(99L))
            .isInstanceOf(UserNotFoundException.class);
    }
}

Controller 层测试

使用 MockMvc 模拟 HTTP 请求

java 复制代码
@WebMvcTest(UserController.class)
class UserControllerTest {
    
    @Autowired
    private MockMvc mockMvc;
    
    @MockBean
    private UserService userService;

    @Test
    void getUser_ValidId_Returns200() throws Exception {
        User mockUser = new User(1L, "test@example.com");
        when(userService.getUserById(1L)).thenReturn(mockUser);

        mockMvc.perform(get("/api/users/1"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.email").value("test@example.com"));
    }

    @Test
    void createUser_InvalidInput_Returns400() throws Exception {
        String invalidJson = "{ \"email\": \"bad-email\" }";
        
        mockMvc.perform(post("/api/users")
                .contentType(MediaType.APPLICATION_JSON)
                .content(invalidJson))
            .andExpect(status().isBadRequest());
    }
}

Repository 层测试
注意: 真实数据库交互属于集成测试。单元测试可使用内存数据库:

java 复制代码
@DataJpaTest
class UserRepositoryTest {
    
    @Autowired
    private TestEntityManager entityManager;
    
    @Autowired
    private UserRepository userRepository;

    @Test
    void findByEmail_WhenExists_ReturnsUser() {
        // 1. 准备数据
        User savedUser = entityManager.persistFlushFind(
            new User(null, "test@example.com"));
        
        // 2. 执行查询
        Optional<User> result = userRepository.findByEmail("test@example.com");
        
        // 3. 验证结果
        assertThat(result).isPresent();
        assertThat(result.get().getId()).isNotNull();
    }
}
三、关键测试场景设计
层级 测试场景 验证要点
Service 业务逻辑分支 异常处理/事务边界/条件覆盖
Controller 请求验证 HTTP状态码/响应体/错误处理
Util 纯逻辑组件 算法正确性/边界条件
四、最佳实践
  1. 命名规范
    被测方法_测试条件_预期结果 模式
java 复制代码
@Test
void transferFunds_InsufficientBalance_ThrowsException() { ... }

测试隔离

使用 @BeforeEach 重置测试状态:

java 复制代码
@BeforeEach
void setup() {
    reset(mockDependency); // 重置Mock状态
}

验证异常

JUnit 5 异常断言:

java 复制代码
assertThrows(ValidationException.class, 
    () -> service.processRequest(invalidRequest));

参数化测试

覆盖多组输入:

java 复制代码
@ParameterizedTest
@ValueSource(strings = {"2023-01-01", "2023-13-01", "invalid-date"})
void parseDate_InvalidInput_ThrowsException(String input) {
    assertThrows(DateTimeParseException.class, 
        () -> DateUtils.parse(input));
}
五、测试覆盖率优化
  • 使用 JaCoCo 检查覆盖率:
XML 复制代码
<plugin>
  <groupId>org.jacoco</groupId>
  <artifactId>jacoco-maven-plugin</artifactId>
  <version>0.8.8</version>
  <executions>
      <execution>
          <goals>
              <goal>prepare-agent</goal>
              <goal>report</goal>
          </goals>
      </execution>
  </executions>
</plugin>
  • 推荐目标

    • 服务层:≥ 80%

    • 关键工具类:100%

    • Controller:验证核心状态码(非强制100%)

关键原则:单元测试应聚焦当前组件的责任,避免跨层验证。通过 Mock 隔离依赖,确保测试快速执行(单个测试类 < 1秒)。结合集成测试覆盖整体流程,形成完整的测试金字塔。

相关推荐
Albert Edison2 小时前
【最新版】IntelliJ IDEA 2025 创建 SpringBoot 项目
java·spring boot·intellij-idea
Piper蛋窝3 小时前
深入 Go 语言垃圾回收:从原理到内建类型 Slice、Map 的陷阱以及为何需要 strings.Builder
后端·go
六毛的毛5 小时前
Springboot开发常见注解一览
java·spring boot·后端
AntBlack6 小时前
拖了五个月 ,不当韭菜体验版算是正式发布了
前端·后端·python
31535669136 小时前
一个简单的脚本,让pdf开启夜间模式
前端·后端
uzong6 小时前
curl案例讲解
后端
开开心心就好7 小时前
免费PDF处理软件,支持多种操作
运维·服务器·前端·spring boot·智能手机·pdf·电脑
一只叫煤球的猫7 小时前
真实事故复盘:Redis分布式锁居然失效了?公司十年老程序员踩的坑
java·redis·后端
猴哥源码7 小时前
基于Java+SpringBoot的农事管理系统
java·spring boot
大鸡腿同学8 小时前
身弱武修法:玄之又玄,奇妙之门
后端