使用 PowerMockito 模拟 getClass 方法
我想避免模拟类的 getClass() 方法,但似乎找不到任何解决方法.我正在尝试测试一个将 HashMap 中的对象类类型存储到稍后使用的特定方法的类.一个简单的例子是:
I'd like to avoid mocking the getClass() method for a class but cannot seem to find any way around it. I'm trying to test a class that stores objects class types in a HashMap to a particular method to be used later. A brief example of this is:
public class ClassToTest {
/** Map that will be populated with objects during constructor */
private Map<Class<?>, Method> map = new HashMap<Class<?>, Method>();
ClassToTest() {
/* Loop through methods in ClassToTest and if they return a boolean and
take in an InterfaceA parameter then add them to map */
}
public void testMethod(InterfaceA obj) {
final Method method = map.get(obj.getClass());
boolean ok;
if (method != null) {
ok = (Boolean) method.invoke(this, obj);
}
if (ok) {
obj.run();
}
}
public boolean isSafeClassA(final ClassA obj) {
// Work out if safe to run and then return true/false
}
public boolean isSafeClassB(final ClassB obj) {
// Work out if safe to run and then return true/fals
}
}
public interface InterfaceA {
void run()
}
public class ClassA implements InterfaceA {
public void run() {
// implements method here
}
}
public class ClassB implements InterfaceA {
public void run() {
// implements method here
}
}
然后我有一个看起来有点像这样的 JUnit 测试:
I then have a JUnit test that looks a little like this:
@RunWith(PowerMockRunner.class)
@PrepareForTest({ClassA.class})
public class ClassToTestTest {
private final ClassToTest tester = new ClassToTest();
@Test
public void test() {
MockGateway.MOCK_GET_CLASS_METHOD = true;
final ClassA classA = spy(new ClassA());
doReturn(ClassA.class).when(classA).getClass();
MockGateway.MOCK_GET_CLASS_METHOD = false;
tester.testMethod(classA);
verify(classA).run();
}
}
我的问题是虽然在 test() 方法中 classA.getClass();将返回 ClassA,一旦进入测试人员的 testMethod() 方法,它仍会返回 ClassA$EnhancerByMockitoWithCGLIB$... 类,因此我的有用对象将始终为空.
My problem is although inside the test() method classA.getClass(); will return ClassA, once inside tester's testMethod() method it still returns the ClassA$EnhancerByMockitoWithCGLIB$... class and so my object useful will always be null.
有什么办法可以绕过模拟课程或者我需要做什么来解决这个问题?
Is there any way I can get around mocking the class or what do I need to do to fix this?
提前致谢.
推荐答案
哇,让这段代码可测试真是令人头疼.主要问题是您不能在对 map.get(obj.getClass())
的调用中使用模拟对象作为 key
对象,而您正在尝试invoke()
可能会为您的测试模拟对象.我必须重构您的测试类,以便我们可以模拟功能/行为并能够验证其行为.
Wow, what a headache to get this code testable. The main issues are that you can't use mock objects as key
objects into your calls to map.get(obj.getClass())
, and you're trying to invoke()
potentially mock objects for your testing. I had to refactor your class under test so that we can mock out the functionality/behaviour and be able to verify its behaviour.
所以这是你的新实现,要使用成员变量进行测试,将各种功能解耦并由测试类注入
So this is your new implementation to be tested with member variables decoupling the various pieces of functionailty and injected by the test class
public class ClassToTest {
MethodStore methodStore;
MethodInvoker methodInvoker;
ClassToInvoke classToInvoke;
ObjectRunner objectRunner;
public void testMethod(InterfaceA obj) throws Exception {
Method method = methodStore.getMethod(obj);
boolean ok = false;
if (method != null) {
ok = methodInvoker.invoke(method, classToInvoke, obj);
}
if (ok) {
objectRunner.run(obj);
}
}
public void setMethodStore(MethodStore methodStore) {
this.methodStore = methodStore;
}
public void setMethodInvoker(MethodInvoker methodInvoker) {
this.methodInvoker = methodInvoker;
}
public void setObjectRunner(ObjectRunner objectRunner) {
this.objectRunner = objectRunner;
}
public void setClassToInvoke(ClassToInvoke classToInvoke) {
this.classToInvoke = classToInvoke;
}
}
这是您不再需要 PowerMock 的测试类,因为它无法模拟方法类.它只是返回一个 NullPointerException.
This is your test class that no longer requires PowerMock, because it can't mock the Method class. It just returns a NullPointerException.
public class MyTest {
@Test
public void test() throws Exception {
ClassToTest classToTest = new ClassToTest();
ClassA inputA = new ClassA();
// trying to use powermock here just returns a NullPointerException
// final Method mockMethod = PowerMockito.mock(Method.class);
Method mockMethod = (new ClassToInvoke()).getClass().getMethod("someMethod"); // a real Method instance
// regular mockito for mocking behaviour
ClassToInvoke mockClassToInvoke = mock(ClassToInvoke.class);
classToTest.setClassToInvoke(mockClassToInvoke);
MethodStore mockMethodStore = mock(MethodStore.class);
classToTest.setMethodStore(mockMethodStore);
when(mockMethodStore.getMethod(inputA)).thenReturn(mockMethod);
MethodInvoker mockMethodInvoker = mock(MethodInvoker.class);
classToTest.setMethodInvoker(mockMethodInvoker);
when(mockMethodInvoker.invoke(mockMethod,mockClassToInvoke, inputA)).thenReturn(Boolean.TRUE);
ObjectRunner mockObjectRunner = mock(ObjectRunner.class);
classToTest.setObjectRunner(mockObjectRunner);
// execute test method
classToTest.testMethod(inputA);
verify(mockObjectRunner).run(inputA);
}
}
你需要的附加类如下
public class ClassToInvoke {
public void someMethod() {};
}
public class ClassA implements InterfaceA {
@Override
public void run() {
// do something
}
}
public class ClassToInvoke {
public void someMethod() {};
}
public class MethodInvoker {
public Boolean invoke(Method method, Object obj, InterfaceA a) throws Exception {
return (Boolean) method.invoke(obj, a);
}
}
public class MethodStore {
Map<Class<?>, Method> map = new HashMap<Class<?>, Method>();
public Method getMethod(InterfaceA obj) {
return map.get(obj);
}
}
将所有这些放入您的 IDE 中,它会通过一个绿色条...哇哦!
Put all this into your IDE and it will pass with a Green bar...woohoo!
相关文章