Mockito + JUnit5 深度集成详解

Mockito + JUnit5 深度集成详解

Mockito 和 JUnit5 是现代 Java 测试的黄金组合,提供了强大的单元测试能力。下面我将详细解析它们的集成使用,并通过完整示例代码展示最佳实践。

一、集成架构与核心优势

JUnit5
测试生命周期管理
参数化测试
扩展模型
Mockito
Mock 创建
行为验证
Stubbing
集成核心
MockitoExtension
自动注入
注解驱动
无缝协作

核心优势

  • 简洁注解@Mock@InjectMocks 自动管理依赖
  • 生命周期集成:Mock 对象与测试方法生命周期同步
  • 参数化扩展:结合 Mockito 进行复杂场景测试
  • 异常处理:统一管理测试异常验证

二、Maven 依赖配置

<dependencies>
    <!-- JUnit5 -->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <version>5.9.0</version>
        <scope>test</scope>
    </dependency>
    
    <!-- Mockito Core -->
    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-core</artifactId>
        <version>4.11.0</version>
        <scope>test</scope>
    </dependency>
    
    <!-- Mockito JUnit5 Integration -->
    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-junit-jupiter</artifactId>
        <version>4.11.0</version>
        <scope>test</scope>
    </dependency>
    
    <!-- AssertJ for Fluent Assertions -->
    <dependency>
        <groupId>org.assertj</groupId>
        <artifactId>assertj-core</artifactId>
        <version>3.23.1</version>
        <scope>test</scope>
    </dependency>
</dependencies>

三、核心注解详解

1. @ExtendWith(MockitoExtension.class)

启用 Mockito 与 JUnit5 的集成

2. @Mock

创建模拟对象:

@Mock
private UserRepository userRepository;

3. @Spy

创建部分真实对象:

@Spy
private EmailService emailService;

4. @InjectMocks

自动注入依赖:

@InjectMocks
private UserService userService;

5. @Captor

创建参数捕获器:

@Captor
private ArgumentCaptor<User> userCaptor;

四、完整业务场景示例

业务模型

public class User {
    private Long id;
    private String username;
    private String email;
    private boolean active;
    
    // Constructors, getters, setters
}

public enum UserStatus {
    ACTIVATED, DEACTIVATED, PENDING
}

服务层

public interface UserRepository {
    User save(User user);
    Optional<User> findById(Long id);
    List<User> findByStatus(UserStatus status);
}

public class EmailService {
    public void sendActivationEmail(String email) {
        // 实际发送邮件逻辑
    }
}

public class UserService {
    private final UserRepository userRepository;
    private final EmailService emailService;
    
    public UserService(UserRepository userRepository, EmailService emailService) {
        this.userRepository = userRepository;
        this.emailService = emailService;
    }
    
    public User registerUser(String username, String email) {
        User user = new User(username, email);
        User savedUser = userRepository.save(user);
        emailService.sendActivationEmail(savedUser.getEmail());
        return savedUser;
    }
    
    public User activateUser(Long userId) {
        User user = userRepository.findById(userId)
            .orElseThrow(() -> new UserNotFoundException("User not found"));
        
        user.setActive(true);
        return userRepository.save(user);
    }
    
    public List<User> getPendingUsers() {
        return userRepository.findByStatus(UserStatus.PENDING);
    }
}

五、测试类实现

基础测试类结构

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.Mockito.*;
import static org.assertj.core.api.Assertions.*;

@ExtendWith(MockitoExtension.class)
class UserServiceTest {

    @Mock
    private UserRepository userRepository;

    @Mock
    private EmailService emailService;

    @InjectMocks
    private UserService userService;

    // 测试用例将在这里实现
}

1. 用户注册测试

@Test
void registerUser_ValidInput_CreatesUserAndSendsEmail() {
    // 准备测试数据
    String username = "john_doe";
    String email = "john@example.com";
    User savedUser = new User(1L, username, email, false);
    
    // 配置mock行为
    when(userRepository.save(any(User.class))).thenReturn(savedUser);
    
    // 执行测试方法
    User result = userService.registerUser(username, email);
    
    // 验证结果
    assertThat(result)
        .isNotNull()
        .extracting(User::getId, User::getUsername, User::getEmail)
        .containsExactly(1L, username, email);
    
    // 验证交互
    verify(userRepository).save(any(User.class));
    verify(emailService).sendActivationEmail(email);
    verifyNoMoreInteractions(userRepository, emailService);
}

2. 用户激活测试

@Test
void activateUser_ExistingUser_ActivatesUser() {
    // 准备测试数据
    Long userId = 1L;
    User inactiveUser = new User(userId, "inactive_user", "inactive@example.com", false);
    User activeUser = new User(userId, "active_user", "active@example.com", true);
    
    // 配置mock行为
    when(userRepository.findById(userId)).thenReturn(Optional.of(inactiveUser));
    when(userRepository.save(any(User.class))).thenReturn(activeUser);
    
    // 执行测试方法
    User result = userService.activateUser(userId);
    
    // 验证结果
    assertThat(result.isActive()).isTrue();
    
    // 验证交互
    verify(userRepository).findById(userId);
    verify(userRepository).save(argThat(user -> 
        user.getId().equals(userId) && user.isActive()
    );
}

3. 异常场景测试

@Test
void activateUser_NonExistingUser_ThrowsException() {
    // 准备测试数据
    Long userId = 999L;
    
    // 配置mock行为
    when(userRepository.findById(userId)).thenReturn(Optional.empty());
    
    // 执行并验证异常
    assertThatThrownBy(() -> userService.activateUser(userId))
        .isInstanceOf(UserNotFoundException.class)
        .hasMessageContaining("User not found");
    
    // 验证交互
    verify(userRepository).findById(userId);
    verifyNoInteractions(emailService);
    verifyNoMoreInteractions(userRepository);
}

4. 参数捕获器使用

@Test
void registerUser_CapturesCorrectUserData() {
    // 准备测试数据
    String username = "captured_user";
    String email = "captured@example.com";
    
    // 配置mock行为
    when(userRepository.save(any(User.class))).thenAnswer(invocation -> {
        User user = invocation.getArgument(0);
        return new User(1L, user.getUsername(), user.getEmail(), false);
    });
    
    // 执行测试方法
    userService.registerUser(username, email);
    
    // 捕获参数
    ArgumentCaptor<User> userCaptor = ArgumentCaptor.forClass(User.class);
    verify(userRepository).save(userCaptor.capture());
    
    // 验证捕获的参数
    User capturedUser = userCaptor.getValue();
    assertThat(capturedUser)
        .extracting(User::getUsername, User::getEmail, User::isActive)
        .containsExactly(username, email, false);
}

5. Spy 部分真实对象

@Test
void registerUser_WithSpyEmailService() {
    // 使用Spy包装真实对象
    EmailService realEmailService = new EmailService();
    EmailService emailServiceSpy = spy(realEmailService);
    
    // 重新创建UserService注入Spy
    UserService userService = new UserService(userRepository, emailServiceSpy);
    
    // 配置mock行为
    User savedUser = new User(1L, "spy_user", "spy@example.com", false);
    when(userRepository.save(any(User.class))).thenReturn(savedUser);
    
    // 执行测试方法
    userService.registerUser("spy_user", "spy@example.com");
    
    // 验证交互
    verify(emailServiceSpy).sendActivationEmail("spy@example.com");
    
    // 注意:实际邮件发送方法会被调用
    // 如要阻止实际调用,可使用:
    // doNothing().when(emailServiceSpy).sendActivationEmail(anyString());
}

6. BDD 风格测试 (Given-When-Then)

import static org.mockito.BDDMockito.*;

@Test
void getPendingUsers_BDDStyle() {
    // Given
    User pendingUser1 = new User(1L, "pending1", "p1@example.com", false);
    User pendingUser2 = new User(2L, "pending2", "p2@example.com", false);
    List<User> pendingUsers = Arrays.asList(pendingUser1, pendingUser2);
    
    given(userRepository.findByStatus(UserStatus.PENDING))
        .willReturn(pendingUsers);
    
    // When
    List<User> result = userService.getPendingUsers();
    
    // Then
    then(userRepository).should().findByStatus(UserStatus.PENDING);
    assertThat(result)
        .hasSize(2)
        .extracting(User::getUsername)
        .containsExactly("pending1", "pending2");
}

7. 参数化测试 + Mockito

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

@ParameterizedTest
@CsvSource({
    "john, john@example.com",
    "alice, alice@company.org",
    "bob, bob@gmail.com"
})
void registerUser_Parameterized(String username, String email) {
    // 准备测试数据
    User savedUser = new User(1L, username, email, false);
    
    // 配置mock行为
    when(userRepository.save(any(User.class))).thenReturn(savedUser);
    
    // 执行测试方法
    User result = userService.registerUser(username, email);
    
    // 验证结果
    assertThat(result.getEmail()).isEqualTo(email);
    
    // 验证交互
    verify(userRepository).save(argThat(user -> 
        user.getUsername().equals(username) && 
        user.getEmail().equals(email)
    ));
    verify(emailService).sendActivationEmail(email);
}

六、高级技巧与最佳实践

1. 初始化方法优化

@BeforeEach
void setUp() {
    // 复杂Mock初始化
    when(userRepository.save(any(User.class)))
        .thenAnswer(invocation -> {
            User user = invocation.getArgument(0);
            return new User(ThreadLocalRandom.current().nextLong(1, 100), 
                          user.getUsername(), 
                          user.getEmail(), 
                          false);
        });
}

2. 自定义 Answer 实现

@Test
void activateUser_WithCustomAnswer() {
    // 自定义Answer保存调用记录
    List<User> activatedUsers = new ArrayList<>();
    
    when(userRepository.save(any(User.class)))
        .thenAnswer(invocation -> {
            User user = invocation.getArgument(0);
            activatedUsers.add(user);
            return user;
        });
    
    // 测试执行...
    
    // 验证自定义Answer
    assertThat(activatedUsers)
        .hasSize(1)
        .allMatch(User::isActive);
}

3. 超时验证

@Test
void highConcurrencyActivation() {
    // 配置mock行为
    when(userRepository.findById(anyLong())).thenReturn(Optional.of(new User()));
    when(userRepository.save(any(User.class))).thenReturn(new User());
    
    // 模拟并发调用
    IntStream.range(0, 100).parallel().forEach(i -> {
        userService.activateUser((long) i);
    });
    
    // 验证并发调用
    verify(userRepository, timeout(1000).atLeast(99)).save(any(User.class));
}

4. Mockito + AssertJ 组合断言

@Test
void complexUserValidation() {
    User result = userService.registerUser("test", "test@example.com");
    
    assertThat(result)
        .isNotNull()
        .satisfies(user -> {
            assertThat(user.getId()).isPositive();
            assertThat(user.getUsername()).isEqualTo("test");
            assertThat(user.getEmail()).contains("@");
        });
    
    verify(emailService).sendActivationEmail(endsWith("example.com"));
}

七、测试生命周期管理

测试类MockitoExtensionMockito框架@BeforeAll初始化框架状态@BeforeEach重置Mock状态@Test 方法执行测试逻辑@AfterEach验证交互@AfterAll清理资源测试类MockitoExtensionMockito框架

八、常见问题解决方案

问题解决方案
NPE 未初始化 Mock添加 @ExtendWith(MockitoExtension.class)
Mock 行为未生效检查 Stubbing 顺序(先配置后执行)
参数匹配器使用错误避免在验证中使用原始值,改用 any() 系列方法
多次调用返回相同值使用链式 thenReturn()thenAnswer()
静态方法模拟失败升级 Mockito 4.x+ 使用 mockStatic
集成 Spring 测试问题使用 @MockBean 替代 @Mock

九、企业级最佳实践

1. 测试命名规范

// 反模式
@Test
void test1() {}

// 最佳实践
@Test
void registerUser_WhenEmailInvalid_ThrowsValidationException() {}

@Test
void activateUser_WithExpiredToken_ReturnsErrorResponse() {}

2. 分层测试策略

class UserServiceTest { // 单元测试: Mock所有依赖
    
    @Mock UserRepository repo;
    @Mock EmailService email;
    @InjectMocks UserService service;
}

@SpringBootTest
class UserServiceIntegrationTest { // 集成测试: 部分真实依赖
    
    @Autowired UserService service;
    @MockBean EmailService emailService;
}

@DataJpaTest
class UserRepositoryTest { // 持久层测试: 真实数据库
    @Autowired UserRepository repo;
}

3. 测试代码结构

@Test
void businessOperation_SuccessScenario() {
    // 第一阶段: Arrange - 准备测试数据和Mock行为
    given(dependency.performOperation(any())).willReturn(expectedResult);
    
    // 第二阶段: Act - 执行被测方法
    ActualResult result = testSubject.executeBusinessOperation(input);
    
    // 第三阶段: Assert - 验证结果和交互
    assertThat(result).isEqualTo(expected);
    then(dependency).should().performOperation(input);
}

十、总结

Mockito + JUnit5 核心价值

  1. 简洁高效:注解驱动减少样板代码
  2. 深度集成:无缝结合现代测试特性
  3. 灵活验证:支持复杂交互验证场景
  4. 可读性强:BDD 风格提升测试可读性
  5. 扩展性强:支持自定义验证规则

最佳实践建议

  • 对每个测试类使用 @ExtendWith(MockitoExtension.class)
  • 遵循 Given-When-Then 结构组织测试
  • 结合 AssertJ 创建流畅断言
  • 对核心业务逻辑保持 80%+ 覆盖率
  • 为复杂领域对象创建自定义断言
  • 在 CI/CD 流水线中强制执行测试

完整测试栈示例:

@ExtendWith(MockitoExtension.class)
class CompleteTestExample {

    @Mock
    private Dependency dependency;
    
    @InjectMocks
    private ServiceUnderTest service;
    
    @Test
    void fullFeatureTest() {
        // Given
        given(dependency.getData(anyString())).willReturn("mockData");
        
        // When
        Result result = service.process("input");
        
        // Then
        assertThat(result)
            .isNotNull()
            .hasFieldOrPropertyWithValue("status", SUCCESS)
            .extracting(Result::getDetails)
            .isInstanceOf(Details.class);
        
        verify(dependency, times(1)).getData("input");
    }
}

通过 Mockito 和 JUnit5 的深度集成,开发者可以构建健壮、可维护的测试套件,有效保障 Java 应用程序的质量和稳定性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值