您的位置:首页 >Mockito Spy方法未调用问题解析
发布于2025-10-16 阅读(0)
扫一扫,手机访问

在单元测试中,Mockito 的 spy 功能允许我们对一个真实对象进行部分模拟,即可以调用真实对象的方法,也可以对特定方法进行桩(stub)或验证(verify)。然而,一个常见的误解是,当生产代码(即被测试的代码)内部直接创建了它所依赖的对象实例时,测试中创建的 spy 对象将无法生效。
考虑以下生产代码片段,其中 MyService 类直接创建了 GetOptionBidPrice 的实例:
// 生产代码
public class MyService {
public double calculateBidPrice() {
// 问题所在:MyService 内部直接实例化 GetOptionBidPrice
GetOptionBidPrice getOptionBidPrice = new GetOptionBidPrice(/* 构造参数 */);
double bidPrice = getOptionBidPrice.getBidPrice();
return bidPrice;
}
}
// 被依赖的类
public class GetOptionBidPrice {
// 假设有构造函数和getBidPrice方法
public GetOptionBidPrice(/* 构造参数 */) {
// ... 初始化 ...
}
public double getBidPrice() {
// 真实业务逻辑,可能返回0.0或其他默认值
return 0.0;
}
}在尝试测试 MyService 时,我们可能会这样编写测试代码,试图桩化 getBidPrice 方法:
// 测试代码 (错误示范)
import org.junit.jupiter.api.Test;
import static org.mockito.Mockito.*;
public class MyServiceTest {
@Test
void testCalculateBidPriceWithSpy() {
// 1. 创建 MyService 实例
MyService myService = new MyService();
// 2. 创建 GetOptionBidPrice 的间谍对象并桩化其方法
// 注意:这里创建的 spyGetOptionBidPrice 是一个独立的实例
GetOptionBidPrice spyGetOptionBidPrice = spy(new GetOptionBidPrice(/* 构造参数 */));
doReturn(100.0).when(spyGetOptionBidPrice).getBidPrice();
// 3. 调用 MyService 的方法
double result = myService.calculateBidPrice();
// 预期结果是 100.0,但实际结果会是 0.0
// 因为 myService 内部创建了一个全新的 GetOptionBidPrice 实例,而不是我们桩化的 spyGetOptionBidPrice
System.out.println("实际结果: " + result); // 输出 0.0
// assertEquals(100.0, result); // 断言失败
}
}问题根源: 上述测试代码的问题在于,MyService 内部的 calculateBidPrice 方法在执行时,会通过 new GetOptionBidPrice() 语句重新创建一个全新的 GetOptionBidPrice 实例。这个新实例与我们在测试中创建并桩化的 spyGetOptionBidPrice 实例是完全不同的两个对象。因此,MyService 内部调用的 getBidPrice() 方法是新实例的真实方法,而不是间谍对象上被桩化的方法,导致桩化操作未能生效。
解决这一问题的核心思想是:将依赖对象的创建和管理从被依赖类中分离出来,让被依赖类不再直接负责创建其依赖。这种设计模式被称为依赖注入(Dependency Injection, DI)。通过依赖注入,我们可以在测试时注入一个 spy 或 mock 对象,而在生产环境中注入真实对象。
依赖注入有多种实现方式,最常见的是构造函数注入和方法注入。
这是最推荐的方式,尤其适用于强制性依赖。被依赖对象在构造时就接收其所有依赖。
改造后的生产代码:
// 生产代码 (使用构造函数注入)
public class MyService {
private final GetOptionBidPrice getOptionBidPrice;
// 依赖通过构造函数注入
public MyService(GetOptionBidPrice getOptionBidPrice) {
this.getOptionBidPrice = getOptionBidPrice;
}
public double calculateBidPrice() {
// 现在 MyService 使用的是注入进来的 GetOptionBidPrice 实例
return getOptionBidPrice.getBidPrice();
}
}改造后的测试代码:
// 测试代码 (使用构造函数注入)
import org.junit.jupiter.api.Test;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class MyServiceTest {
@Test
void testCalculateBidPriceWithInjectedSpy() {
// 1. 创建 GetOptionBidPrice 的间谍对象并桩化其方法
GetOptionBidPrice spyGetOptionBidPrice = spy(new GetOptionBidPrice(/* 构造参数 */));
doReturn(100.0).when(spyGetOptionBidPrice).getBidPrice();
// 2. 将间谍对象注入到 MyService 实例中
MyService myService = new MyService(spyGetOptionBidPrice);
// 3. 调用 MyService 的方法
double result = myService.calculateBidPrice();
// 现在,MyService 内部调用的是我们注入的间谍对象的 getBidPrice() 方法
assertEquals(100.0, result);
verify(spyGetOptionBidPrice).getBidPrice(); // 验证方法是否被调用
}
}如果依赖不是每次操作都必须的,或者需要在特定方法执行时才提供,可以使用方法注入。
改造后的生产代码:
// 生产代码 (使用方法注入)
public class MyService {
public double calculateBidPrice(GetOptionBidPrice getOptionBidPrice) {
// 依赖通过方法参数注入
return getOptionBidPrice.getBidPrice();
}
}改造后的测试代码:
// 测试代码 (使用方法注入)
import org.junit.jupiter.api.Test;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class MyServiceTest {
@Test
void testCalculateBidPriceWithMethodInjectedSpy() {
// 1. 创建 GetOptionBidPrice 的间谍对象并桩化其方法
GetOptionBidPrice spyGetOptionBidPrice = spy(new GetOptionBidPrice(/* 构造参数 */));
doReturn(100.0).when(spyGetOptionBidPrice).getBidPrice();
// 2. 创建 MyService 实例
MyService myService = new MyService();
// 3. 调用 MyService 的方法时,将间谍对象作为参数传入
double result = myService.calculateBidPrice(spyGetOptionBidPrice);
assertEquals(100.0, result);
verify(spyGetOptionBidPrice).getBidPrice();
}
}除了解决 spy 不生效的问题,依赖注入还带来了多方面的好处:
当您在使用 Mockito 的 spy 或 mock 功能时发现桩化或模拟的行为未能按预期执行,一个首要的排查方向就是检查您的生产代码是否直接实例化了其依赖。如果是,那么依赖注入就是解决这个问题的关键。
关键点:
通过采纳依赖注入模式,不仅能有效解决 Mockito spy 的使用问题,更能从根本上提升代码的质量、可测试性和可维护性,是现代软件开发中不可或缺的设计实践。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
9