Erik's Weblog 2.0

January 29, 2006 +1 more...

[@418]

Testing Private Methods

I've been playing with TestNG for a few days now. I really like it. It makes me want to write more tests.

I was wondering if there was a way to create a test for a private method, so I went to the source. I asked Cédric about it. He mentioned that I should use the Reflection API.

The goal was to write a test for the validate method in the following class:
package net.thauvin.erik;

public class Example {
  // ...
  
  private static int validate(String username, String password, int days) {
    // ...
    
    return days;
  }
}
In order to invoke the validate method in my test, I simply needed to: as follows:
package net.thauvin.erik;

import org.testng.annotations.*;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ExampleTest {
  // ...

  @Test
  public void validateTest() throws NoSuchMethodException,
      IllegalAccessException, InvocationTargetException {
    final Method validate = Example.class.getDeclaredMethod("validate",
        String.class, String.class, int.class);
    validate.setAccessible(true);

    assert (new Integer(10).equals(validate.invoke(validate, "test",
        "passw0rd"new Integer(10))));
  }
}
Pretty nifty trick.
[@514]

Bob emailed me the following:

I would just make the method package-private (default access) and put the test in the same package.

Bob

As I told him, friendly access doesn't always work for me because of the way I obfuscate private methods. I could make exceptions in my obfuscator settings, but that's a lot more work.

You might want to read Bob's post on Package Scoping and Unit Testing.

[@395]

Eugene rightfully pointed out that Commons Lang provides helpers to reflectively invoke methods.

Comments

CruxicJan 20, 2007 at 11:11

Here's a method I just figured out that might be useful for solving this problem:

1) In your Example class declare a public static inner class, like so:

public class Example {
...
  public static class Tester{
    @Test
    public void testValidate(){
      assertTrue(validate("test", "passw0rd, 10) == 10);
    }
  }
}

2) In your testng.xml file you can call it like so:
   ...
  <classes>
    <class name="myPackage.Example$Tester"/>
  </classes>
  ...

OR
  ...
  <packages>
    <package name="myPackage"/>
  </packages>
  ...

Unfortunately the following does not seem to work in TestNG 5.4:
   ...
  <classes>
    <class name="myPackage.Example"/>
  </classes>
  ...

Perhaps that's a bug.

The awesome thing about this is that your Example class does NOT need to have a public non-arg constructor.  This allows you to do this kind of white box testing with very little boiler plate code.  The one possible downside is that your testing logic is hard to exclude from your release builds because it's part of the class.  Personally, I can accept that because I place higher value on grouping related information - the class and it's unit test.  Besides, a person could probably filter out unit test code at compile time with Ant's Filter task.

Post a comment

 

Comment Preview