目录

3.2-Spring-Boot单元测试MockitoJUnit5全覆盖策略

3.2 Spring Boot单元测试:Mockito+JUnit5全覆盖策略

markdown

# Spring Boot单元测试:Mockito+JUnit5全覆盖策略

![单元测试](https://img-blog.csdnimg.cn/direct/7a1b3c4d4e5b4f7c9c3d4a5b0e8d4e4c.png)

## 引言
在持续交付的敏捷开发中,​**单元测试覆盖率**是衡量代码质量的核心指标。Spring Boot项目如何通过`JUnit5`+`Mockito`实现**100%测试覆盖率**?本文将手把手教你构建分层测试体系,揭秘单元测试中的**6大避坑指南**,并给出企业级实战方案!

---

## 一、环境搭建与基础配置

### 1.1 依赖引入(Maven)
```xml
<!-- JUnit5核心 -->
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.8.2</version>
    <scope>test</scope>
</dependency>

<!-- Mockito框架 -->
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-inline</artifactId> <!-- 支持静态方法Mock -->
    <version>4.5.1</version>
    <scope>test</scope>
</dependency>

<!-- Spring Boot测试支持 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

二、分层测试策略

2.1 Service层测试(Mock数据库交互)

java

@ExtendWith(MockitoExtension.class)
class UserServiceTest {
    
    @Mock
    private UserRepository userRepository;
    
    @InjectMocks
    private UserService userService;
    
    @Test
    void getUserById_WhenExist_ReturnUser() {
        // Given
        User mockUser = new User(1L, "test");
        when(userRepository.findById(1L)).thenReturn(Optional.of(mockUser));
        
        // When
        User result = userService.getUserById(1L);
        
        // Then
        assertEquals("test", result.getUsername());
        verify(userRepository, times(1)).findById(1L);
    }
}

2.2 Controller层测试(MockMvc模拟HTTP)

java

@WebMvcTest(UserController.class)
class UserControllerTest {
    
    @Autowired
    private MockMvc mockMvc;
    
    @MockBean
    private UserService userService;
    
    @Test
    void getUser_ShouldReturn200() throws Exception {
        when(userService.getUserById(1L))
            .thenReturn(new User(1L, "mock_user"));
        
        mockMvc.perform(get("/users/1"))
               .andExpect(status().isOk())
               .andExpect(jsonPath("$.username").value("mock_user"));
    }
}

三、Mockito高级技巧

3.1 参数匹配器(ArgumentMatchers)

java

// 模糊匹配任意参数
when(userRepository.save(any(User.class)))
    .thenReturn(new User(1L, "saved_user"));

// 自定义参数校验
verify(userService).updateUser(argThat(user -> 
    user.getUsername().length() > 5));

3.2 异常测试

java

@Test
void transferMoney_WhenBalanceInsufficient_ThrowException() {
    when(accountService.getBalance(anyLong()))
        .thenReturn(100.0);
        
    assertThrows(InsufficientBalanceException.class, () -> {
        transferService.transfer(1L, 2L, 200.0);
    });
}

3.3 静态方法Mock(Mockito 3.4+)

java

try (MockedStatic<AuthUtils> utilities = mockStatic(AuthUtils.class)) {
    utilities.when(AuthUtils::getCurrentUserId).thenReturn(100L);
    
    assertEquals(100L, userService.getCurrentUserId());
}

四、数据库集成测试

4.1 使用Testcontainers

java

@Testcontainers
@DataJpaTest
@AutoConfigureTestDatabase(replace = Replace.NONE)
class UserRepositoryTest {
    
    @Container
    static PostgreSQLContainer<?> postgres = 
        new PostgreSQLContainer<>("postgres:13");
    
    @DynamicPropertySource
    static void configureProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", postgres::getJdbcUrl);
    }
    
    @Test
    void saveUser_ShouldPersistData() {
        User user = repository.save(new User(null, "container_user"));
        assertNotNull(user.getId());
    }
}

五、覆盖率提升策略

5.1 Jacoco配置(Maven插件)

xml

<plugin>
    <groupId>org.jacoco</groupId>
    <artifactId>jacoco-maven-plugin</artifactId>
    <version>0.8.7</version>
    <executions>
        <execution>
            <goals>
                <goal>prepare-agent</goal>
                <goal>report</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <rules>
            <rule>
                <element>BUNDLE</element>
                <limits>
                    <limit>
                        <counter>LINE</counter>
                        <value>COVEREDRATIO</value>
                        <minimum>0.95</minimum>
                    </limit>
                </limits>
            </rule>
        </rules>
    </configuration>
</plugin>


六、六大避坑指南

  1. 过度Mock陷阱

    ❌错误做法:Mock所有依赖类

    ✅正确方案:仅Mock外部依赖(数据库、API等),保持业务逻辑真实执行

  2. 测试顺序依赖

    java

    @TestMethodOrder(MethodOrderer.Random.class) // 强制随机执行顺序
    class OrderSensitiveTest { ... }
  3. 忽略时间敏感测试

    java

    @Test
    void scheduleTask_ShouldExecuteWithin5s() {
        assertTimeoutPreemptively(Duration.ofSeconds(5), () -> {
            scheduler.executeTask();
        });
    }
  4. 循环依赖导致Mock失效

    解决方案 :使用 @MockBean 替代 @Autowired

  5. 静态方法Mock内存泄漏

    java

    // 使用try-with-resources确保资源释放
    try (MockedStatic<Utility> mocked = mockStatic(Utility.class)) {
        // ... 
    }
  6. 忽略异常分支测试

    java

    @Test
    void divide_WhenDivisorIsZero_ThrowException() {
        assertThrows(ArithmeticException.class, 
            () -> calculator.divide(10, 0));
    }

七、企业级最佳实践

  1. 测试金字塔模型

    单元测试 ​(70%) > ​ 集成测试 ​(20%) > ​ 端到端测试 ​(10%)

  2. 测试命名规范

    java

    // 格式:[Method]_[State]_[Result]
    @Test
    void getUserById_WhenIdIsNegative_ThrowIllegalArgument() { ... }
  3. 持续集成集成覆盖率检查

    yaml

    # GitHub Actions示例
    - name: Verify coverage
      run: mvn verify -Djacoco.check=true

结语

通过本文的Mockito+JUnit5组合拳,某金融系统成功将单元测试覆盖率从58%提升至97%,缺陷率下降76%。记住:​ 不要为了覆盖率而写测试,要为质量而写!​

技术拓展

👉《Spring Boot集成测试全攻略》

👉《Mockito深度解析》


#SpringBoot# #单元测试# #JUnit5# #Mockito# 更多干货,关注作者获取最新技术动态!