mockito入门

简介: 本内容主要介绍Mockito框架的使用,包括快速上手指南、案例分析和高级用法。涵盖Mockito资源链接、依赖配置及版本要求(4.x支持JDK1.8,5.x需JDK11+)。通过具体代码示例,讲解了Spy与Mock对象的区别及应用场景,如创建真实或虚拟对象、模拟方法调用等。同时深入探讨了做桩技术,包括对具体参数和任意参数的处理,并提供注解方式简化测试代码。此外,针对私有方法的Mock需求,介绍了PowerMockito扩展框架及反射技术的实现方式,强调了设计优化的重要性,建议通过重构避免直接Mock私有方法,以提升测试健壮性和代码可维护性。最后附有参考资料供进一步学习。

Quick Start

概念

Mockito资源

案例

使用Mockito

<dependency>
   <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>{version}</version>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <scope>test</scope>
</dependency>

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<Product> products;

    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 版本。

引入依赖

<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-module-junit4</artifactId>
    <version>2.0.9</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-api-mockito2</artifactId>
    <version>2.0.9</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>3.12.4</version>
    <scope>test</scope>
</dependency>

示例

@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 私有方法?

  1. 破坏封装性:私有方法属于类的内部实现细节,Mock 它会将测试与实现紧密耦合。
  2. 测试脆弱性:当私有方法逻辑变化时,测试会失败,即使公有行为未变。
  3. 设计问题信号:过度依赖 Mock 私有方法可能意味着类职责过重,需考虑重构(如将私有方法提取到新类中)。

方式3: 替代方案:重构代码

若必须测试私有方法,更好的做法是:

  1. 将私有方法提取到新类:通过依赖注入使用新类,Mock 新类的公共方法。

    public class YourClass {
        private Helper helper; // 原私有方法提取到 Helper 类
    
        public String publicMethod() {
            return helper.privateMethodLogic();
        }
    }
    
  2. 测试 Helper 类:直接测试 Helper 类的公共方法。

总结

场景 方案
无法修改遗留代码 使用 PowerMockito(方式 1)
可重构代码 提取私有方法到新类,通过依赖注入 Mock
临时测试需求 谨慎使用反射(方式 2)

尽量通过设计优化避免 Mock 私有方法,优先保证测试的健壮性和代码的可维护性。

参考资料

[1] Mockito教程

相关文章
Mockito框架抛出NullPointerException
一文详细讲解Mockito框架是怎么抛出NullPointerException的整个过程和解决方式。
7116 0
|
9月前
|
安全 Java 测试技术
《深入理解Spring》单元测试——高质量代码的守护神
Spring测试框架提供全面的单元与集成测试支持,通过`@SpringBootTest`、`@WebMvcTest`等注解实现分层测试,结合Mockito、Testcontainers和Jacoco,保障代码质量,提升开发效率与系统稳定性。
|
Java 测试技术 开发者
必学!Spring Boot 单元测试、Mock 与 TestContainer 的高效使用技巧
【10月更文挑战第18天】 在现代软件开发中,单元测试是保证代码质量的重要手段。Spring Boot提供了强大的测试支持,使得编写和运行测试变得更加简单和高效。本文将深入探讨Spring Boot的单元测试、Mock技术以及TestContainer的高效使用技巧,帮助开发者提升测试效率和代码质量。
1537 2
|
测试技术
Squaretest自动生成单元测试
Squaretest自动生成单元测试
1057 8
Squaretest自动生成单元测试
|
Java 编译器 Spring
面试突击78:@Autowired 和 @Resource 有什么区别?
面试突击78:@Autowired 和 @Resource 有什么区别?
18075 7
|
Java 测试技术 Maven
Java一分钟之-PowerMock:静态方法与私有方法测试
通过本文的详细介绍,您可以使用PowerMock轻松地测试Java代码中的静态方法和私有方法。PowerMock通过扩展Mockito,提供了强大的功能,帮助开发者在复杂的测试场景中保持高效和准确的单元测试。希望本文对您的Java单元测试有所帮助。
2484 2
|
人工智能 Java 测试技术
JDK11下Mock框架进化:从PowerMockito到Mockito Only
本文探讨了从使用PowerMock的测试环境迁移到仅使用Mockito(Mockito Only)策略的必要性和实践方法。
913 10
|
监控 Java 测试技术
Mockito教程
Mockito教程
2937 0
Mockito教程
|
XML Java Maven
MockedStatic 用于模拟静态方法
`MockedStatic` 是 Mockito 3.4.0 版本引入的一个功能,用于模拟静态方法。在之前的 Mockito 版本中,模拟静态方法是一项困难的任务,通常需要借助其他工具如 PowerMockito。但是,从 Mockito 3.4.0 开始,你可以使用 `MockedStatic` 类轻松地模拟静态方法。 以下是如何使用 `MockedStatic` 模拟静态方法的示例: 1. 首先,确保你已经添加了 Mockito 的依赖项。对于 Maven 项目,添加以下依赖项: ```xml <dependency> <groupId>org.mockito</groupId
3936 9
|
SQL 存储 JSON
Cassandra CQL语法 以及功能介绍
内容摘要: 一、Cassandra简单介绍 ·Cassandra的历史和基本架构 二、Cassandra CQL介绍 ·如何使用CQL语言操作Cassandra
5734 0
Cassandra CQL语法 以及功能介绍