Tuesday, March 26, 2013

Testing a private method through Reflection of JMockit

I came across a private method that I wanted to unit test. It was a lynch-pin in the algorithm and needed to be tested independently of its callers to resolve an issue I was experiencing with it. Usually you will test private methods indirectly by thoroughly testing the public methods with enough variation in the parameters to make you reasonably sure that you have tested all states the private method can be put through. But sometimes you just want to test the private method directly.

One way is to make it public or protected. However, I don't think changing the visibility of a method just to enable testing is a good design choice.

Or you could use Java Reflection. Or you could use JMockit De-encapsulation (which uses reflection). JMockit's de-encapsulation is intended to allow mocking of private methods, but it can be used to invoke them directly as well.

Below I will give examples of how to invoke a method using both reflection and JMockit de-encapsulation.

First is the example class with a private method, ripe for the testing.

public class StringParser {
   private String[] parseString(final String listOfNames) {
      return listOfNames.split(",");
   }
}

And here is my test code.

import static org.junit.Assert.assertArrayEquals;
import java.lang.reflect.Method;
import mockit.Deencapsulation;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

@RunWith(JUnit4.class)
public final class TestStringParser {

   /** Test a private method with JMockit. */
   @Test
   public void testStringParsingJmockit() {
      // Create the object containing the private method I will test.
      StringParser tested = new StringParser();
      // This is the result I expect to receive from the method being tested.
      String[] expected = { "one", "two", "three" };
      // Invoke the private method, getting the actual results.
      // tested: the object containing the private method.
      // "parseString": name of the private method.
      // "one,two,three": the argument I want to send to the private method.
      String[] actual =
            Deencapsulation.invoke(tested, "parseString", "one,two,three");
      // Test that the expected results match the actual results.
      assertArrayEquals("Parsed list not as expected.", expected, actual);
   }

   /** Test a private method with Java Reflection.
    * @throws Exception
    *            if there are issues with Reflection such as method not existing
    */
   @Test
   public void testParsingForCommas() throws Exception {
      // Create the object containing the private method I will test.
      StringParser tested = new StringParser();
      // This is the result I expect to receive from the method being tested.
      String[] expected = { "one", "two", "three" };
      // Create a "Method" object representing the private method I will test.
      // "parseString": name of the private method.
      // String.class: the TYPE of argument I want to send to the private method.
      Method declaredMethod =
            tested.getClass().getDeclaredMethod("parseString", String.class);
      // Tell Java to make the private method callable by outside code.
      declaredMethod.setAccessible(true);
      // Invoke the private method, getting the actual results.
      // tested: the object containing the private method.
      // "one,two,three": the argument I want to send to the private method.
      String[] actual =
            (String[]) declaredMethod.invoke(tested, "one,two,three");
      // Test that the expected results match the actual results.
      assertArrayEquals("Parsed list not as expected.", expected, actual);
   }

}

I received help on how to do the JMockit code from a couple of very useful forums.