【保姆级】Mockito核心注解实战指南:单元测试从入门到规范

187 阅读3分钟

在Java单元测试中,Mockito是模拟依赖的首选工具。本文将全面解析@Mock@Spy@Captor@InjectMocks四大核心注解的使用场景与最佳实践,助你编写出高可维护性的测试代码~


一、环境准备:启用Mockito注解

在测试类中启用注解支持的三种方式:

  1. JUnit Runner(推荐):适合大多数测试场景
@RunWith(MockitoJUnitRunner.class)public class ServiceTest {// 测试方法
}
  1. 手动初始化:灵活控制初始化时机
@Beforepublic void initMocks() {
    MockitoAnnotations.openMocks(this);
}
  1. JUnit Rule:适合需要与其他测试规则配合使用
@Rulepublic MockitoRule mockitoRule = MockitoJUnit.rule();

二、@Mock:创建模拟对象

适用场景:需要完全隔离依赖对象时

public class UserServiceTest {@Mockprivate UserRepository userRepo;
@Testpublic void testFindUser() {// 配置模拟行为when(userRepo.findById(1001L))
           .thenReturn(new User("张三"));
User result = userService.findUser(1001L);
        assertThat(result.getName()).isEqualTo("张三");
    }
}

关键功能: • 模拟方法返回值:when(...).thenReturn(...)

• 模拟异常抛出:when(...).thenThrow(...)

• 验证调用次数:verify(mock, times(2)).method()


三、@Spy:部分真实的对象

适用场景:需要保留对象主要功能,仅修改特定方法时

public class OrderProcessorTest {@Spyprivate InventoryService inventory = new InventoryService();
@Testpublic void testStockCheck() {// 覆盖库存检查方法
        doReturn(50).when(inventory).getStock("ITEM_001");
boolean available = orderProcessor.checkAvailability("ITEM_001", 30);
        assertTrue(available);
    }
}

注意事项:

  1. 优先使用doReturn().when()语法
  2. 避免在@Spy对象上直接调用when(),可能触发真实方法
  3. 适合需要保留部分业务逻辑的测试场景

四、@Captor:参数捕获器

适用场景:需要验证方法调用时传入的具体参数

public class AuditServiceTest {@Mockprivate AuditLogger logger;
    @Captorprivate ArgumentCaptor<LogEntry> logCaptor;
@Testpublic void testLoginAudit() {
        userService.login("user1", "password123");
        verify(logger).record(logCaptor.capture());LogEntry entry = logCaptor.getValue();
        assertThat(entry.getAction()).isEqualTo("LOGIN");
        assertThat(entry.getUsername()).isEqualTo("user1");
    }
}

典型用途:

• 验证复杂对象的属性值

• 检查集合类型参数的内容

• 捕获多次调用的参数序列


五、@InjectMocks:依赖自动注入

适用场景:测试包含多层依赖的复杂服务

public class PaymentServiceTest {@Mockprivate PaymentGateway gateway;
    @Mockprivate TransactionRepo txRepo;
    @InjectMocksprivate PaymentService paymentService;
@Testpublic void testSuccessfulPayment() {when(gateway.process(any()))
           .thenReturn(new PaymentResult(true, "SUCCESS"));
            PaymentResult result = paymentService.execute(new PaymentRequest(100.0, "USD"));

        assertTrue(result.isSuccess());
        verify(txRepo).save(any());
    }
}

注入规则:

  1. 按类型匹配构造函数参数
  2. 支持字段注入(需setter方法)
  3. 优先注入@Mock对象,未找到时尝试实例化真实对象

六、验证规范

验证类型使用场景
verify(mock)基本调用验证
times(n)验证精确调用次数
atLeastOnce()至少一次调用
never()确保从未调用

七、总结

通过合理运用Mockito的四大核心注解,我们可以构建出结构清晰、运行稳定的单元测试体系。关键要平衡测试的隔离性与真实性,既避免过度模拟导致测试失真,也要防止因依赖外部服务造成的测试脆弱性~

最后安利几个小tips:

  1. 注意保持测试单一职责:每个测试方法验证一个逻辑分支
  2. 使用@Before初始化通用配置
@Before
public void setup() {
    when(configService.getTimeout()).thenReturn(30);
}
  1. 用静态导入让代码更清爽
import static org.mockito.Mockito.*;
// 这样就能直接写when()、verify()啦

下次写单元测试时,再来看看这篇文章,保证你的测试代码既简洁又专业(^_^)v~~