Mocking getClass method with PowerMockito

I would like to avoid bullying the getClass () method for the class, but it seems I have not found any way around it. I am trying to test a class that stores object class types in HashMap for a specific method, which will be used later. A brief example:

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 } } 

Then I have a JUnit test that looks something 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(); } } 

My problem is that inside the test () method classA.getClass (); will return ClassA, after using the tester's testMethod () method, it still returns the ClassA $ EnhancerByMockitoWithCGLIB $ ... class, so my useful object will always be null.

Is there a way around the mockery of the class, or what do I need to do to fix this?

Thanks in advance.

+4
source share
3 answers

Wow, what a headache to check this code. The main problems are that you cannot use mock objects as key objects in your calls to map.get(obj.getClass()) , and you try invoke() potentially simulate objects for your testing. I had to reorganize your class into a test so that we could mock functionality / behavior and be able to test its behavior.

So, this is your new implementation that needs to be tested with member variables, decoupling the various parts of the function and injected with a 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; } } 

This is your test class, which no longer requires PowerMock, because it cannot make fun of the Method class . It simply 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); } } 

Additional classes you require are as follows

 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); } } 

Put all this in your development environment and it will go with a green bar ... woohoo!

+4
source

Your problem is that getClass is final in Object , so you cannot drown it with Mockito. I can't think of a good way around this. There is one possibility that you can consider.

Write a utility class that has one method

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

and reorganize the test class so that it uses an object of this utility class instead of directly calling getClass() . Then you can enter the utility object either using the special private-private constructor, or using the setter method.

 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 } 

Now, in your test, mock the object and the getClass stub. Enter the layout in the test class.

+4
source

I came across a similar question. I think you should add ClassToTest.class to @PrepareForTest because you want to make fun of the getClass () function in this class

0
source

Source: https://habr.com/ru/post/1432119/


All Articles