Testing Private Methods in Java

Posted: August 19th, 2011 | Author: | Filed under: General

Java 5 provides vargargs API that allows to test private methods in a very concise way. Here is how.

Yesterday, during a meetup at Loopt, a question of an API for testing private methods was brought up. I used to use JUnit add-on PrivateAccessor [1] when I was testing private methods. The problem was that applying PrivateAccessor produced prohibitively verbose code, so you had to think really hard to understand what the test was doing.

invoke() with varargs

Luckily, Java 5 provides vargargs API that allows to write really concise code for invoking private methods and return results. Here is the invoke() method that makes use of vargargs. Feel free to use it in your code:

   /**
    * Invokes a private method on an object.
    *
    * @param obj        the object on what to invoke the method.
    * @param methodName the name of the method to invoke.
    * @param parameters a list of parameter
    * @return the result of dispatching the method represented by the object.
    */
   public static <T> T invoke(Object obj, String methodName, Object... parameters) {

      try {

         // Create a list of parameter classes
         final Class[] parameterTypes = new Class[parameters.length];
         for (int i = 0; i < parameters.length; i++) {
            parameterTypes[i] = parameters[i].getClass();
         }

         // Get method
         Class clazz = obj.getClass();
         Method method = clazz.getDeclaredMethod(methodName, parameterTypes);
         method.setAccessible(true);

         // Call method
         return (T)method.invoke(obj, parameters);
      } catch (RuntimeException e) {

         throw e;
      } catch (Exception e) {

         throw new IllegalArgumentException(e);
      }
   }

Complete Example

Below is a complete JUnit tests that tests StringBuilder’s private method append(). Notice how neatly the invocation of append() fits into a short single line:

import java.lang.reflect.Method;
import junit.framework.TestCase;

public final class StringBuilderTest extends TestCase {


   /**
    * Object under test
    */
   private StringBuilder object;


   /**
    * Tests String's private method 'append(StringBuilder sb)'.
    */
   public void testAppend() {
      
      StringBuilder toAppend = new StringBuilder("Test");
      StringBuilder result = invoke(object, "append", toAppend);
      assertEquals("Test", result.toString());
   }


   /**
    * Invokes a private method on an object.
    *
    * @param obj        the object on what to invoke the method.
    * @param methodName the name of the method to invoke.
    * @param parameters a list of parameter
    * @return the result of dispatching the method represented by the object.
    */
   public static <T> T invoke(Object obj, String methodName, Object... parameters) {

      try {

         // Create a list of parameter classes
         final Class[] parameterTypes = new Class[parameters.length];
         for (int i = 0; i < parameters.length; i++) {
            parameterTypes[i] = parameters[i].getClass();
         }

         // Get method
         Class clazz = obj.getClass();
         Method method = clazz.getDeclaredMethod(methodName, parameterTypes);
         method.setAccessible(true);

         // Call method
         return (T)method.invoke(obj, parameters);
      } catch (RuntimeException e) {

         throw e;
      } catch (Exception e) {

         throw new IllegalArgumentException(e);
      }
   }


   public void setUp() throws Exception {

      super.setUp();

      object = new StringBuilder(10);
   }
}

Making invoke() reusable

To make invoke() reusable, I suggest to move it to a separate utility class or at least to create a super class for all your tests that extends junit.TestCase and move invoke() there.

Regards,

Slava Imeshev

References

1. JUnit Add-On PrivateAccessor

(7) Comments: Post a response
  1. simeshev said at 12:05 am on August 22nd, 2011:

    Steven,

    I agree. The need for testing a private method cries for making a part of the public API. Though, in this case I someone asked me for help and I though it was worth sharing a solution.

    Slava Imeshev
    Cacheonix: Distributed Java Cache

  2. Michael said at 5:07 am on August 22nd, 2011:

    Very original! I’ll be sure to add your invoke() method to my ReflectionUtils class for testing private methods. Thanks!

  3. JS Mammen said at 5:25 am on August 22nd, 2011:

    The best way to test private method is through the public method. If the private method is not invoked when a specific public method is invoked, then you probably do not want the private method in the first place.

    Would there be a use case where a private method is not invoked by a public method??

  4. Steven Baker said at 11:03 pm on August 21st, 2011:

    Do not test private methods directly. Unit testing is the test on the class from a user of the class’ point of view. This way you can refactor and throw away/create private methods without being afraid of outside code directly using them.

  5. kobrys said at 10:29 am on August 24th, 2011:

    ‘Would there be a use case where a private method is not invoked by a public method??’

    but this is usefull when you follow the Unitty principe (test only one class, all others are mocks)
    it is usefull when you have a package access method which is called only by other package classes.

  6. Sam Brannen said at 6:05 pm on August 24th, 2011:

    Hi Slava,

    That’s a nice tool to have in the developer’s toolbox. I also created similar testing utilities in the Spring Framework in the ReflectionTestUtils class (dating back to 2007).

    In fact, based on a user’s suggestion in Spring’s JIRA instance and your blog post, I just introduced a new, generic invokeMethod(Object, String, Object…) method in Spring’s ReflectionTestUtils. ๐Ÿ˜‰

    While testing my own implementation, I discovered that there is a small deficiency in your implementation: specifically it does not support automatic boxing and unboxing from primitives to wrapper types. Add the following methods to your test case to see what I mean:

    protected Integer add(Integer a, Integer b) {
    return a + b;
    }

    protected int subtract(int a, int b) {
    return a – b;
    }

    public void testAutoboxing() {
    // Passes
    int sum = invoke(this, “add”, 1, 2);
    assertEquals(“add(1,2)”, 3, sum);

    // Fails
    int difference = invoke(this, “subtract”, 5, 3);
    assertEquals(“subtract(5,3)”, 2, difference);
    }

    Luckily for me, I was able to delegate to Spring’s MethodInvoker class that takes care of finding a “matching” method by taking into account that a declared argument may actually be a primitive that “matches” the wrapper type returned by introspection.

    Hopefully this tip will help you in case you come across this issue in your own tests.

    Regards,

    Sam Brannen (author of the Spring TestContext Framework)

  7. simeshev said at 9:57 pm on August 24th, 2011:

    Sam,

    Though this code example may certainly benefit from further development, my goal was to demonstrate some benefits of using vargargs together with the reflection API. I am glad to hear that my post inspired you to add invokeMethod() to your API.

    Regards,

    Slava Imeshev
    Cacheonix: Distributed Java Cache

You must be logged in to post a comment.