世界杯预选赛中国队赛程_世界杯多少年一次 - fybstd.com


mockito入门

2025-06-07 14:26:14 - 中国世界杯夺冠

Quick Start

概念

Mockito资源

官网: mockito.org

项目源码:github.com/mockito/mockito

案例

使用Mockito

org.mockito

mockito-core

{version}

test

junit

junit

test

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

引入依赖

org.powermock

powermock-module-junit4

2.0.9

test

org.powermock

powermock-api-mockito2

2.0.9

test

org.mockito

mockito-core

3.12.4

test

示例

@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教程