Search code examples
javajunitpowermockito

Testing private static method that internally uses another private static method in Java


I have a class A, that looks like the following:

public class A {

  // there is a public API that uses both
  // method1 and method2, but that's not important
  // in this question.
  public Integer publicApi(Integer a, Integer b) {
    // a bunch of other stuff is done that is 
    // not related to method1 or method2 here.
    // However, method1 IS used somewhere in here.
    // For now, I don't want to test other
    // implementation details for this API, I only
    // am interested in testing method1.

    return method1(a, b);
  }

  private static Integer method1(Integer a, Integer b) {
    if(method2(a, b, 10) == null) {
      return -1;
    }
    return 10 + method2(a, b, 10);
  }

  private static Integer method2(Integer a, Integer b, Integer c) {
    // some complicated logic with multiple values 
    // to return (based on various conditions).

    // however, I want to simply test what happens
    // when this method returns null (which is a possibility).
    return a+b+c;
  }
}

As is given, my class A has a public API that's (sort of) complicated logic, that I don't want to test (for the purpose of this question). Say I am only interested in testing method1.

Here is my test code (I'm using PowerMockito):

import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

import java.lang.reflect.Method;
import static org.junit.Assert.assertNull;
import static org.powermock.api.mockito.PowerMockito.spy;
import static org.powermock.api.mockito.PowerMockito.when;
import static org.powermock.api.support.membermodification.MemberMatcher.method;

@RunWith(PowerMockRunner.class)
@PrepareForTest(A.class)
public class SimpleTest {

  @Test
  public void testMethod1Invocation() throws Exception {

    spy(A.class);

    when(A.class, method(A.class,
        "method2", Integer.class,
        Integer.class, Integer.class))
        .withArguments(1, 2, 10)
        .thenReturn(null);

    // use reflection to call method1
    Method method1 = A.class.getDeclaredMethod(
        "method1",
        Integer.class, Integer.class
    );

    method1.setAccessible(true);
    Object ret = method1.invoke(1, 2);
    assertEquals(-1, ret);
  }
}

But, I get the following error:

com.apple.geo.tart.A.method1(java.lang.Integer, java.lang.Integer, java.lang.Integer)
java.lang.NoSuchMethodException: com.apple.geo.tart.A.method1(java.lang.Integer, java.lang.Integer, java.lang.Integer)
    at java.lang.Class.getDeclaredMethod(Class.java:2130)
    at com.apple.geo.tart.SimpleTest.testMethod1Invocation(SimpleTest.java:32)

I don't see what I'm doing wrong here. As it seems to me, there are 3 things that I need here:

  1. Spy on A so that I can mock when/thenReturn on the static methods.
  2. Mock the private static method that I'm interested in (method2 in this case).
  3. Use reflection to access method1, and then invoke it.

How do I then mock a private static method (method2), just so that I can test another private static method(method1) of the same class that depends on the first?


Solution

  • I've got some issues with this code before we talk about the testability of it:

    • method2 will never return null in actuality. If any of the inputs to it are null, you will get a NullPointerException due to Java attempting to unbox null. Therefore, the condition you're attempting to test for is fairly pointless.
    • There's really no value in having these methods hidden. They appear to do business-level calculations, and thus have value in being tested.

    I will say this much for your initial case: I don't get the same error that you do. From what I see with Java 8, you're missing a parameter to your invoke method call, with which you need to pass method1 in.

    Object ret = method1.invoke(method1, 1, 2);
    

    However, I do not recommend this at all. Let's break this apart into a few tests.

    First, let's loosen the restrictions on those methods. They're likely private because we don't to expose their behavior elsewhere, but there's likely no harm if we weaken that to package-private. Remove the private marker from those methods.

    Second, actually test those methods! Those methods can be influenced by a null and you should test against that scenario.

    Third, and only after have you tested the other two methods, should you come back to your publicApi method. At this point, all you care about is that the method was simply invoked with the right parameters.