【问题标题】:Mocking getClass method with PowerMockito使用 PowerMockito 模拟 getClass 方法
【发布时间】:2012-09-03 16:31:58
【问题描述】:

我想避免模拟类的 getClass() 方法,但似乎找不到任何解决方法。我正在尝试测试一个将 HashMap 中的对象类类型存储到稍后使用的特定方法的类。一个简单的例子是:

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 测试:

@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$... 类,因此我的有用对象将始终为空。

有什么办法可以绕过模拟课程或者我需要做什么来解决这个问题?

提前致谢。

【问题讨论】:

  • 看来你不应该首先关心模拟 getClass 。你能发布一些关于测试本身的细节吗?
  • 我同意,您要断言什么? testMethod() 正在返回 void,因此 assert 上没有任何内容,而且看起来您没有更新传入的参数对象。因此我只能假设 testMethod 内部有一些方法调用() 你想verify的互动
  • 我希望使用 mockito 来验证测试中的某些操作是否发生。我的地图实际上将类类型存储到一个方法中,以便我可以为实现接口的每个类调用该方法,然后如果相关方法返回 true(或者该类没有方法),我想确保发生了一些行为。
  • 你能用伪代码编写你的测试(需求),和/或提供更多的实现代码吗?听起来您正在尝试使用map 来实现某种形式的多态行为,这不是一个好方法。如果传入的对象实现了 InterfaceA,那么您应该对 interface 进行编码,而不是尝试确定其实际实现类(例如 ClassA),然后尝试执行未在接口上定义的方法。可能我没有理解正确
  • 我尝试更新我的代码,使其更准确地适应我要测试的内容。我忽略了任何需要捕获/抛出的异常。

标签: java junit mockito powermock


【解决方案1】:

你的问题实际上是getClass 是final 在Object,所以你不能用Mockito 存根它。我想不出解决这个问题的好方法。您可以考虑一种可能性。

编写一个具有单个方法的实用程序类

public Class getClass(Object o){
    return o.getClass();
}

并重构您正在测试的类,以便它使用此实用程序类的对象,而不是直接调用getClass()。然后,可以使用特殊的包私有构造函数或 setter 方法注入实用程序对象。

public class ClassToTest{
    private UtilityWithGetClass utility;
    private Map<Class<?>, Object> map = new HashMap<Class<?>, Object>();

    public ClassToTest() {
        this(new UtilityWithGetClass());
    }

    ClassToTest(UtilityWithGetClass utility){
        this.utility = utility;
        // Populate map here
    }

    // more stuff here
}

现在,在您的测试中,模拟对象和存根 getClass。将模拟注入到您正在测试的类中。

【讨论】:

  • 我认为使用 PowerMockito 可以让我存根最终方法?
  • 是的,PowerMockito 可以。我仍在努力查看您要测试的内容,因此我看不出这个答案有什么用。包装 getClass() 感觉不是正确的做法。
  • 我想验证在给定特定场景的情况下,我作为 InterfaceA 传入的类中的方法(即运行)是否在此方法中被调用。该映射实际上将对象类类型保存到一些安全方法中,并根据这些方法的结果(它们返回一个布尔值)调用类中的 run 方法。
  • 我知道 PowerMockito 可以提供帮助的最终方法是有限制的。我不知道getClass 是否属于你不能存根的那些。我会说“试试看”,但您似乎已经尝试过并看到了。当你说“我作为 InterfaceA 传入的类”时,你的意思是什么——你传递的对象肯定不是一个类?还是这样?
【解决方案2】:

哇,让这段代码可测试真是令人头疼。主要问题是您不能在对map.get(obj.getClass()) 的调用中使用模拟对象作为key 对象,并且您正在尝试invoke() 可能模拟对象以进行测试。我必须重构您的测试类,以便我们可以模拟功能/行为并能够验证其行为。

所以这是你的新实现,要使用成员变量进行测试,将各种功能解耦并由测试类注入

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 的测试类,因为它是can't mock the Method class。它只是返回一个 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 中,它会通过一个绿色条...哇哦!

【讨论】:

    【解决方案3】:

    我也遇到过类似的问题。我认为您应该将 ClassToTest.class 添加到 @PrepareForTest,因为您想模拟该类中的 getClass() 函数

    【讨论】:

      猜你喜欢
      • 2015-03-23
      • 1970-01-01
      • 1970-01-01
      • 2017-07-06
      • 1970-01-01
      • 2017-03-07
      • 1970-01-01
      相关资源
      最近更新 更多