有没有什么办法,用 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"));
}
}
The Singleton
对象是在一个 Foreign
类,在一些测试代码的控制之外(上面没有显示)。这两个类都不能修改。我们的目标是拦截对非静态的 process()
方法,这样对于某些值,就会返回不同的结果,例如调用
Singleton.getInstance(new Object()).process("alpha", "beta");
铩羽而归 "alpha-beta"
而不是预期的 "alphabeta"
.
一种解决方案是拦截 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()
来返回一个部分嘲讽(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]);
}
}
第三,拦截过程方法。
process
召唤@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]);
}
}