
在单元测试中,我们经常使用mocking框架(如mockito)来隔离被测代码,确保测试的焦点仅在于当前逻辑。然而,当被测类(thing)的内部方法(blogic)不通过依赖注入,而是直接在内部创建其依赖对象(如thinggrandparent和thingparent)时,测试就可能面临挑战。
原始测试代码中,Thing类的bLogic方法执行流程如下:
问题在于,测试类ThingTest中声明的@Mock ThingParent tp;和@Mock ThingGrandParent tgpp;这些模拟对象,在t.bLogic(4)的执行过程中从未被使用。t的bLogic方法始终操作的是它自己创建的真实对象。因此,对模拟对象tp设置的when(tgpp.GpFun(anyInt())).thenReturn(tp);虽然语法正确,但由于tgpp这个模拟对象从未被t使用,这行代码的设置也就失去了意义。
当assertEquals(4, t.bLogic(4));执行完毕后,由于tp(模拟对象)的fetchSideThing()方法从未被调用,随后的verify(tp, times(1)).fetchSideThing();自然会失败,并抛出Wanted but not invoked的错误。至于InvocationTargetException,它通常发生在通过反射调用方法时,如果方法内部抛出异常,反射机制会将其封装成InvocationTargetException。在本例中,它可能间接指示了bLogic方法内部执行流程与预期不符,导致了后续的问题。
解决此类问题的核心在于,我们需要让被测对象t在执行bLogic方法时,能够使用我们提供的模拟对象,而不是它自己创建的真实对象。由于Thing类并未提供依赖注入的接口(例如通过构造函数或setter方法传入ThingGrandParent),我们需要利用Mockito的spy功能来模拟Thing对象自身的内部方法调用。
when(t.fun(anyInt())).thenReturn(tgpp);
通过这行代码,当t.bLogic()内部调用fun(size)时,它将不再创建新的ThingGrandParent,而是直接获得我们预设的模拟对象tgpp。
when(tgpp.GpFun(anyInt())).thenReturn(tp);
这样,t.bLogic()就能获得模拟的tp对象。
when(tp.fetchSideThing()).thenReturn(SideThing.get(4));
或者,如果SideThing的创建逻辑简单,也可以直接返回一个真实的SideThing实例:when(tp.fetchSideThing()).thenReturn(new SideThing(4));
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
// 原始类定义(此处省略,与问题描述中相同)
// ... SideThing, ThingGrandParent, ThingParent, Thing ...
@RunWith(PowerMockRunner.class)
@PowerMockIgnore("javax.management.*")
@PrepareForTest({Thing.class, SideThing.class}) // PowerMock 相关注解,用于处理更复杂的模拟场景,如静态、final、构造函数等
public class ThingTest {
@Mock
ThingParent tp;
@Mock
ThingGrandParent tgpp;
Thing t;
// 使用 MockitoRule 替代废弃的 MockitoAnnotations.initMocks()
@Rule
public MockitoRule rule = MockitoJUnit.rule();
@Before
public void setup() {
// MockitoAnnotations.initMocks(this); // 此方法已废弃,使用 @Rule 替代
t = spy(new Thing()); // t 是一个真实对象,但其方法可以被部分模拟
}
@Test
public void test1() throws Exception {
// 1. 模拟 t.fun() 方法,使其返回我们期望的模拟 tgpp 对象
when(t.fun(anyInt())).thenReturn(tgpp);
// 2. 模拟 tgpp.GpFun() 方法,使其返回我们期望的模拟 tp 对象
when(tgpp.GpFun(anyInt())).thenReturn(tp);
// 3. 模拟 tp.fetchSideThing() 方法,使其返回一个带有预期权重的 SideThing 对象
// 这样,t.bLogic() 最终会得到这个 SideThing,并调用其 getWeight()
when(tp.fetchSideThing()).thenReturn(SideThing.get(4));
// 执行被测方法
assertEquals(4, t.bLogic(4));
// 验证模拟对象的交互
verify(tp, times(1)).fetchSideThing();
verify(tgpp, times(1)).GpFun(anyInt()); // 也可以验证 tgpp 的调用
verify(t, times(1)).fun(anyInt()); // 也可以验证 t 的 fun 方法的调用
}
}原始代码中使用了MockitoAnnotations.initMocks(this);,但该方法已被标记为废弃。在现代Mockito和JUnit集成中,推荐使用MockitoRule(对于JUnit 4)或MockitoExtension(对于JUnit 5)来初始化Mock对象。
对于JUnit 4: 在测试类中添加@Rule注解和MockitoJUnit.rule():
import org.junit.Rule;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
public class ThingTest {
@Rule
public MockitoRule rule = MockitoJUnit.rule();
// ... 其他代码 ...
}MockitoRule会自动处理@Mock、@Spy等注解的初始化,无需手动调用initMocks。
对于JUnit 5: 使用@ExtendWith(MockitoExtension.class):
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
public class ThingTest {
// ... 其他代码 ...
}问题中提到了spy和InjectMocks的区别,以及在本例中如何选择。
在本案例中,Thing类内部直接调用new ThingGrandParent()和new ThingParent()来创建依赖,它并没有可供InjectMocks注入的ThingGrandParent或ThingParent字段。因此,InjectMocks在这里不适用,spy是更合适的选择,因为它允许我们拦截并模拟Thing对象内部对fun()方法的调用。
以上就是Mockito/PowerMock测试中内部依赖模拟的陷阱与解决方案的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号