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


    org.junit.jupiter
    junit-jupiter
    5.8.2
    test




    org.mockito
    mockito-inline 
    4.5.1
    test




    org.springframework.boot
    spring-boot-starter-test
    test

二、分层测试策略

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 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


    org.jacoco
    jacoco-maven-plugin
    0.8.7
    
        
            
                prepare-agent
                report
            
        
    
    
        
            
                BUNDLE
                
                    
                        LINE
                        COVEREDRATIO
                        0.95
                    
                
            
        
    

https://img-blog.csdnimg.cn/direct/9a1b3c4d4e5b4f7c9c3d4a5b0e8d4e4c.png


六、六大避坑指南

  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 mocked = mockStatic(Utility.class)) {
        // ... 
    }
  6. 忽略异常分支测试

     

    java

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

七、企业级最佳实践

  1. 测试金字塔模型
    https://img-blog.csdnimg.cn/direct/8a1d3c4d4a5a4d6c9b3e7c8d9f4e4b4a.png
    单元测试​(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# 更多干货,关注作者获取最新技术动态!

你可能感兴趣的:(Spring,boot,从入门到精通,零基础7天精通Spring,Boot,spring,boot,单元测试,log4j)