使用Mockito
或PowerMockito
,是否有任何方法可以拦截对对象的非静态方法的调用,或者至少是对单例对象的调用?
以下类提供了一个示例:
public class Singleton {
private static Singleton INSTANCE = null;
private Singleton(Object parameter) {}
public static Singleton getInstance(Object parameter) {
if (INSTANCE == null) {
INSTANCE = new Singleton(parameter);
}
return INSTANCE;
}
public String process(String a, String b) {
return (a + b);
}
// Other methods
}
public class Foreign {
private Foreign() {}
public static void main(String[] args) {
System.out.println(Singleton.getInstance(new Object()).process("alpha", "beta"));
}
}
Singleton
对象是在Foreign
类中创建的,不受某些测试代码(上面未显示(的控制。这两个类都不能修改。目标是拦截测试代码中对非静态process()
方法的调用,以便对于某些值,返回不同的结果,例如调用
Singleton.getInstance(new Object()).process("alpha", "beta");
以返回CCD_ 6而不是期望的CCD_。
一种解决方案可以是拦截Singleton.getInstance()
方法来实例化Singleton的自定义子类,例如使用
public class SubSingleton extends Singleton {
public SubSingleton(Object parameter) {
super(parameter);
}
public String process(String a, String b) {
if ("alpha".equals(a) && "beta".equals(b)) {
return a + "-" + b;
}
return super.process(a + b);
}
}
然后,对Singleton.process()
方法的调用将被截获,如下所示:
Object parameter = new Object();
PowerMockito.doReturn(new SubSingleton(parameter)).when(Singleton.class, "getInstance", parameter);
但是,上面的Singleton
类只提供了一个私有构造函数,因此无法扩展。使用PowerMockito.whenNew()
返回部分mock(spy(也不起作用,因为Singleton
类不提供无参数构造函数。
想要的嘲讽可以用其他方式实现吗?可以对非单例类执行此操作吗?
首先,您可以将whenNew用于带有一些参数的构造函数的对象:
@RunWith(PowerMockRunner.class)
@PrepareForTest(Singleton.class)
public class SingletonPrivateNewTest {
@Mock
Singleton singletonMock;
@Before
public void setUp() throws Exception {
PowerMockito.whenNew(Singleton.class)
.withAnyArguments()
.thenReturn(singletonMock);
}
@Test
public void testMockNew() throws Exception {
Mockito.when(singletonMock.process(anyString(), anyString())).thenReturn("sasa");
Foreign.main(new String[0]);
}
}
其次,为什么不使用stub getInstance而不是new:
@RunWith(PowerMockRunner.class)
@PrepareForTest(Singleton.class)
public class SingletonPrivateNewTest {
@Test
public void testMockNew() {
PowerMockito.mockStatic(Singleton.class);
Singleton singletonMock = Mockito.mock(Singleton.class);
PowerMockito.when(Singleton.getInstance(any())).thenReturn(singletonMock);
Mockito.when(singletonMock.process(anyString(), anyString())).thenReturn("sasa");
Foreign.main(new String[0]);
}
}
第三,拦截处理方法:
- 创建真正的singleton
- 创建模拟单例
- mock static getInstance返回mock。注意:您必须在获得真实实例后调用mockStatic
- 使用thenAnswer检查
process
调用上的参数- 如果匹配所需模式,则返回所需答案
- else在实单例上调用实方法
@RunWith(PowerMockRunner.class)
@PrepareForTest(Singleton.class)
public class SingletonPrivateNewTest {
@Test
public void testMockNew() {
var singletonReal = Singleton.getInstance(new Object());
var singletonMock = Mockito.mock(Singleton.class);
PowerMockito.mockStatic(Singleton.class);
PowerMockito.when(Singleton.getInstance(any())).thenReturn(singletonMock);
Mockito.when(singletonMock.process(anyString(), anyString())).thenAnswer((args) -> {
String a = args.getArgument(0);
String b = args.getArgument(1);
if ("alpha".equals(a) && "beta".equals(b)) {
return "sasa";
} else {
return singletonReal.process(a, b);
}
});
Foreign.main(new String[0]);
}
}
最后,使用间谍而不是模拟
@RunWith(PowerMockRunner.class)
@PrepareForTest(Singleton.class)
public class SingletonPrivateNewTest {
@Test
public void testMockNew() {
var singletonReal = Singleton.getInstance(new Object());
var singletonMock = Mockito.spy(singletonReal);
PowerMockito.mockStatic(Singleton.class);
PowerMockito.when(Singleton.getInstance(any())).thenReturn(singletonMock);
Mockito.when(singletonMock.process("alpha", "beta")).thenReturn("sasa");
// NOTE: real method is called for other args
Foreign.main(new String[0]);
}
}