Quick Start
概念
Mockito资源
官网: mockito.org
项目源码:github.com/mockito/mockito
案例
使用Mockito
4.x 支持JDK1.8
5.x 需要JDK11+
Demo类
实体类
import lombok.Getter;
import lombok.Setter;
@Setter
@Getter
public class Product {
private String itemId;
private Integer MaxStock;
private Integer availableStock;
public Product(String itemId, int i, int i1) {
this.itemId = itemId;
this.MaxStock = i;
this.availableStock = i1;
}
public void reduceStock(int num) {
this.availableStock -= num;
}
}
@Data
public class Order {
private String orderId;
}
@Data
public class BookInfo {
private String itemId;
private Integer bookNum;
public BookInfo(String number, int i) {
this.itemId = number;
this.bookNum = i;
}
}
服务类
public class OrderService {
private ProductService productService;
public Order book(BookInfo bookInfo){
//query后面要考
Product query = new Product(bookInfo.getItemId(), 0, 0);
Product product = productService.getProduct(query);
Order order = this.buildOrder(bookInfo,product);
if (this.isAvailable(bookInfo,product)){
product.reduceStock(bookInfo.getBookNum());
}
return order;
}
private boolean isAvailable(BookInfo bookInfo, Product product) {
return product.getAvailableStock() >= bookInfo.getBookNum();
}
private Order buildOrder(BookInfo bookInfo, Product product) {
Order order = new Order();
order.setOrderId(UUID.randomUUID().toString().replaceAll("-",""));
return order;
}
public void setProductService(ProductService productService) {
this.productService = productService;
}
}
public class ProductService {
public ProductRepository productRepository;
public Product getProduct(String itemId)
{
return productRepository.selectProduct(itemId);
}
public Product getProduct(Product product)
{
return productRepository.selectProduct(product.getItemId());
}
}
public class ProductRepository {
private List
public ProductRepository() {
this.products = new ArrayList<>();
products.add(new Product("1", 100, 100));
products.add(new Product("2", 100, 100));
products.add(new Product("3", 100, 100));
products.add(new Product("4", 100, 100));
products.add(new Product("5", 100, 100));
}
public Product selectProduct(String itemId) {
for (Product product : products) {
if (product.getItemId().equals(itemId)) {
return product;
}
}
return null;
}
}
使用Spy
使用Spy创建对象是一个真实对象,可以真实执行对象方法。
@Test
public void testSpy(){
OrderService orderService = new OrderService();
OrderService spy = Mockito.spy(orderService);
//会抛出空指针异常:真实调用方法,因为spy对象没有被初始化注入其依赖的类:ProductService
Order book = spy.book(new BookInfo("1", 1));
//会抛出空指针异常:真实调用方法,在mock方法中,没有被初始化注入其依赖的类:ProductService
Mockito.when(spy.book(Mockito.any(BookInfo.class))).thenReturn(new Order());
//不会抛出异常,mock是一个虚拟对象,不会调用真实方法
OrderService mock = Mockito.mock(OrderService.class);
Order book1 = mock.book(new BookInfo("1", 1));
}
使用Mock
使用Mock创建的对象是一个虚拟对象,不会执行代码里面的逻辑。一般Mock对象不单独使用,而是注入到Spy对象或真实对象用以模拟函数调用。
public class OrderServiceTest {
@Test
public void testBook() throws Exception {
OrderService orderService = Mockito.spy(OrderService.class);
ProductService productService = Mockito.mock(ProductService.class);
//通过set方法注入ProductService的mock对象
orderService.setProductService(productService);
/**
1.定义桩数据product
2.桩数据的对应参数(桩参数)stubParam
3.对productService.getProduct做桩
*/
Product product = new Product("1", 100, 100);
Product stubParam = new Product("1", 0, 0);
Mockito.when(productService.getProduct(stubParam)).thenReturn(product);
BookInfo bookInfo = new BookInfo("1", 50);
Order order = orderService.book(bookInfo);
//断言
Assert.assertEquals(50L,(long)product.getAvailableStock());
}
@Test
public void testBook2() throws Exception {
OrderService orderService = new OrderService();
ProductService productService = Mockito.mock(ProductService.class);
//通过反射注入桩对象,下面可通过注解方式避免反射
Field productService1 =
orderService.getClass().getDeclaredField("productService");
productService1.setAccessible(true);
productService1.set(orderService,productService);
//获取orderService的spy实例
OrderService spy = Mockito.spy(orderService);
BookInfo bookInfo = new BookInfo("1", 50);
Order order = orderService.book(bookInfo);
//断言
Assert.assertEquals(50L,(long)product.getAvailableStock());
}
}
做桩
对具体参数
实例2.1
@Test
public void testSub3(){
ProductService productService = Mockito.mock(ProductService.class);
Product product = new Product("1", 100, 100);
Product subParam = new Product("1", 0, 0);
Mockito.when(productService.getProduct(subParam))
.thenReturn(product);
Product product2 = new Product("2", 100, 100);
Product subParam2 = new Product("2", 0, 0);
Mockito.when(productService.getProduct(subParam2))
.thenReturn(product2);
Product product1 = productService.getProduct(subParam);
Product product2 = productService.getProduct(subParam2);
//false product1 product2为不同实例
System.out.println(product1==product2);
}
Mockito如何判接口入参是做桩的参数
先看下面Product的区别,@Data相对于@Getter,@Setter会做更多的事情,比如重写toString()方法。
@Setter
@Getter
public class Product {
//省略...
}
@Data
public class Product {
//省略...
}
在orderService.book()里面在查询产品对象是根据itemId新建了一个query对象。因此,subParm 和 query必然不是同一个实例。实验表明【实例2.1】的执行结果,使用注解@Data时能够获取productService的mock结果,因为subParm 和 query的toString()结果是一样的。使用@Getter,@Setter 无法获取mock结果且toString()结果不一样
猜想:mock是根据参数的toString()来判断参数是否一致,进而返回桩数据
进一步验证猜想:
@Data
public class Product {
//省略...
}
Product subParam2 = new Product("2", 0, 0);
Product subParam3 = new Product("2", 2, 0);
//...
Product query = new Product("2", 0, 0);
// subParam2 可以获取mock结果
// subParam3 未获取mock结果
@Setter
@Getter
public class Product {
//省略...
@Override
public String toString() {
return itemId;
}
}
Product subParam2 = new Product("2", 0, 0);
Product subParam3 = new Product("2", 2, 0);
//...
Product query = new Product("2", 0, 0);
// subParam2 可以获取mock结果
// subParam3 未获取mock结果
因此猜想是错误的!!!!!
有空再看源码吧
任意参数
@Test
public void testSub3(){
ProductService productService = Mockito.mock(ProductService.class);
Product product = new Product("1", 100, 100);
Mockito.when(productService.getProduct(Mockito.any(Product.class)))
.thenReturn(product);
Product subParam = new Product("1", 0, 0);
Product subParam2 = new Product("2", 0, 0);
Product product1 = productService.getProduct(subParam);
Product product2 = productService.getProduct(subParam2);
System.out.println(product1==product2); //true product1 product2都为product实例
}
使用注解
//在Mockito上下文运行
@RunWith(MockitoJUnitRunner.class)
public class AnnotationOrderServiceTest {
@Spy
@InjectMocks
public OrderService orderService;
@Mock
private ProductService productService;
@Before
public void setup(){
//在单元测试前在开启mock
MockitoAnnotations.openMocks(this);
}
@Test
public void testBook() throws Exception {
//桩数据对象
Product product = new Product("1", 10, 10);
//做桩参数
Product subParm = new Product("1", 0, 0);
Mockito.when(productService.getProduct(productQ)).thenReturn(product);
BookInfo bookInfo = new BookInfo("1", 2);
Order book = orderService.book(bookInfo);
Assert.assertEquals(8L,(long)product.getAvailableStock());
}
}
验证(待完善)
断言
异常
Mock私有方法(AI提供的参考方案)
在单元测试中,直接 Mock 私有方法 并不是推荐的做法(违背面向对象设计原则),但在一些特殊场景(如遗留代码无法重构)可能需要此操作。以下是两种技术实现方式,但需注意谨慎使用。
方式 1:使用 PowerMockito(扩展 Mockito)
PowerMockito 是 Mockito 的扩展框架,支持 Mock 私有方法、静态方法等。
powermock最新版本为2020年更新的2.0.9,且不兼容mockito 4.x以上版本,因此如果使用powermock需要匹配的mockito版本
PowerMock 版本
Mockito 版本
兼容性
2.0+
2.7.0 - 3.x
兼容
1.7.4
1.10.19+
兼容
1.6.x
1.9.5 - 1.10.x
兼容
1.5.x
1.8.5
兼容
注意事项:PowerMock 的版本号不仅与 Mockito 版本号相关,还与使用的 Java 版本有关。建议在使用 PowerMock 时,同时选择与其兼容的 Mockito 版本。
引入依赖
示例
@RunWith(PowerMockRunner.class) // 使用 PowerMock 的 Runner
@PrepareForTest(OrderService.class) // 声明需要增强的类
public class YourClassTest {
@Test
public void testPrivateMethod() throws Exception {
OrderService orderService = PowerMockito.spy(new OrderService()); // 创建 Spy 对象
ProductService productService = Mockito.mock(ProductService.class);
Product product = new Product("1", 100, 100);
Mockito.when(productService.getProduct(Mockito.any(Product.class)))
.thenReturn(product);
orderService.setProductService(productService);
// Mock 私有方法:isAvailable
PowerMockito.doReturn(false)
.when(orderService, "isAvailable", Mockito.any(), Mockito.any());
// 调用公有方法(内部会调用私有方法)
BookInfo bookInfo = new BookInfo("1",1);
Order result = orderService.book(bookInfo);
// 验证结果
Assert.assertNotNull(result);
}
}
关键点
@PrepareForTest:标记需要增强字节码的类(包含私有方法的类)。
PowerMockito.spy():创建对象的 Spy,允许部分 Mock。
doReturn().when():通过反射指定私有方法的行为
方式2:通过反射修改私有方法行为(不推荐)
如果不想依赖 PowerMockito,可以通过反射直接修改私有方法的行为,但代码更复杂且易出错。
示例
public class YourClassTest {
@Test
public void testPrivateMethod() throws Exception {
YourClass obj = new YourClass();
// 获取私有方法
Method privateMethod = YourClass.class.getDeclaredMethod("privateMethodName", String.class);
privateMethod.setAccessible(true); // 突破访问限制
// 通过反射调用私有方法,并 Mock 返回值(需结合动态代理等技巧)
// 此方法复杂且不够直观,通常不推荐
}
}
为什么不建议 Mock 私有方法?
破坏封装性:私有方法属于类的内部实现细节,Mock 它会将测试与实现紧密耦合。
测试脆弱性:当私有方法逻辑变化时,测试会失败,即使公有行为未变。
设计问题信号:过度依赖 Mock 私有方法可能意味着类职责过重,需考虑重构(如将私有方法提取到新类中)。
方式3: 替代方案:重构代码
若必须测试私有方法,更好的做法是:
将私有方法提取到新类:通过依赖注入使用新类,Mock 新类的公共方法。
public class YourClass {
private Helper helper; // 原私有方法提取到 Helper 类
public String publicMethod() {
return helper.privateMethodLogic();
}
}
测试 Helper 类:直接测试 Helper 类的公共方法。
总结
场景
方案
无法修改遗留代码
使用 PowerMockito(方式 1)
可重构代码
提取私有方法到新类,通过依赖注入 Mock
临时测试需求
谨慎使用反射(方式 2)
尽量通过设计优化避免 Mock 私有方法,优先保证测试的健壮性和代码的可维护性。
参考资料
[1] Mockito教程