vendredi 20 janvier 2023

How to mock one private method while testing another private method? PowerMock and reflection in the same test class and in the same test

I have a chain of methods in my class. Here is the simplified version:

public class SomeClass {
    public String run (String someString1, String someString2, String someString3) {
        return firstAction(someString1, someString2, someString3);
    }

    private String firstAction(String someString1, String someString2, String someString3) {
        return someString1 + secondAction(someString2, someString3);
    }

    private String secondAction(String someString2, String someString3) {
        return someString2 + someString3;
    }
}

I can test "secondAction" using the reflection. Or I can test "run" using PowerMock to mock "firstAction". Individual tests work fine. But when I try to test both methods at the same time it ends with error. The same problem in case of testing "firstAction" (using reflection) and mocking "secondAction" (using PowerMock).

@RunWith(PowerMockRunner.class)
@PrepareForTest(SomeClass.class)
public class SomeClassTest {
    private Method getSecondActionMethod() throws NoSuchMethodException {
        Method method = SomeClass.class.getDeclaredMethod("secondAction", String.class, String.class);
        method.setAccessible(true);
        return method;
    }

    @Test
    public void runTest() throws Exception {
        SomeClass someClassSpy = PowerMock.createPartialMock(SomeClass.class, "firstAction");
        PowerMock.expectPrivate(someClassSpy, "firstAction", "Hello", ", ", "World!").andReturn("Hello, World!");
        PowerMock.replay(someClassSpy);
        assertEquals("Hello, World!", someClassSpy.run("Hello", ", ", "World!"));
        PowerMock.verify(someClassSpy);
    }

    @Test
    public void secondActionTest () throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        SomeClass someClass = new SomeClass();
        assertEquals("Hello, World!", getSecondActionMethod().invoke(someClass, "Hello, ", "World!"));
    }
}
    at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:354)
    at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297)
    at java.base/java.lang.reflect.Method.checkCanSetAccessible(Method.java:200)
    at java.base/java.lang.reflect.Method.setAccessible(Method.java:194)
    at org.powermock.reflect.internal.WhiteboxImpl.doGetAllMethods(WhiteboxImpl.java:1508)
    at org.powermock.reflect.internal.WhiteboxImpl.getAllMethods(WhiteboxImpl.java:1482)
    at org.powermock.reflect.internal.WhiteboxImpl.getMethods(WhiteboxImpl.java:1750)
    at org.powermock.reflect.internal.WhiteboxImpl.getMethods(WhiteboxImpl.java:1789)
    at org.powermock.reflect.internal.WhiteboxImpl.getBestMethodCandidate(WhiteboxImpl.java:1008)
    at org.powermock.core.MockInvocation.findMethodToInvoke(MockInvocation.java:58)
    at org.powermock.core.MockInvocation.init(MockInvocation.java:35)
    at org.powermock.core.MockInvocation.<init>(MockInvocation.java:22)
    at org.powermock.core.MockGateway.doMethodCall(MockGateway.java:155)
    at org.powermock.core.MockGateway.methodCall(MockGateway.java:138)
    at somePackage.SomeClassTest.getSecondActionMethod(SomeClassTest.java:26)
    at somePackage.SomeClassTest.secondActionTest(SomeClassTest.java:54)
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
    at java.base/java.lang.reflect.Method.invoke(Method.java:577)
    at org.junit.internal.runners.TestMethod.invoke(TestMethod.java:59)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.runTestMethod(PowerMockJUnit44RunnerDelegateImpl.java:326)
    at org.junit.internal.runners.MethodRoadie$2.run(MethodRoadie.java:79)
    at org.junit.internal.runners.MethodRoadie.runBeforesThenTestThenAfters(MethodRoadie.java:87)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.executeTest(PowerMockJUnit44RunnerDelegateImpl.java:310)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.runBeforesThenTestThenAfters(PowerMockJUnit44RunnerDelegateImpl.java:298)
    at org.junit.internal.runners.MethodRoadie.runTest(MethodRoadie.java:77)
    at org.junit.internal.runners.MethodRoadie.run(MethodRoadie.java:42)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.invokeTestMethod(PowerMockJUnit44RunnerDelegateImpl.java:218)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.runMethods(PowerMockJUnit44RunnerDelegateImpl.java:160)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$1.run(PowerMockJUnit44RunnerDelegateImpl.java:134)
    at org.junit.internal.runners.ClassRoadie.runUnprotected(ClassRoadie.java:27)
    at org.junit.internal.runners.ClassRoadie.runProtected(ClassRoadie.java:37)
    at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.run(PowerMockJUnit44RunnerDelegateImpl.java:136)
    at org.powermock.modules.junit4.common.internal.impl.JUnit4TestSuiteChunkerImpl.run(JUnit4TestSuiteChunkerImpl.java:117)
    at org.powermock.modules.junit4.common.internal.impl.AbstractCommonPowerMockRunner.run(AbstractCommonPowerMockRunner.java:57)
    at org.powermock.modules.junit4.PowerMockRunner.run(PowerMockRunner.java:59)

Please help me to understand this error. Is there another way to test and mock private methods at the same time? Especially when you need to mock a private method called inside another private method. Please do not suggest something like:

  1. Changing private to public/protected.
  2. Combining all private methods into one.
  3. Not to test private methods.




Aucun commentaire:

Enregistrer un commentaire