How to Unit-test private methods

2


I know it’s kind of wrong to test private methods and there are a lot of articles and books that will explain you why. But sometimes you have an old code base where you have to unit-test some private methods to speed up development time. Today I got such a code base. It’s wasn’t that old, but I needed to change a private method that was called by some other methods. Those other methods had some parameters that needed to be set up depending on the user and the actions that user  took in the application.

....

This was way too much work for this simple method I had to change. I knew from a presentation at AMIS that is was possible to change the method accessor at runtime, but I just didn’t knew how. Time to go to our library. I checked some books and no results. On google I found some big articles (I listed the best one at the sources section, definitely a good thing to read when you want to know more about it)

But enough talking, let me show you what I did after some reading and debugging:

<code><br /><font color="#7f0055"><strong>public&nbsp;</strong></font><font color="#7f0055"><strong>void&nbsp;</strong></font><font color="#000000">testSecretIngredient</font><font color="#000000">()&nbsp;</font><font color="#7f0055"><strong>throws&nbsp;</strong></font><font color="#000000">Exception&nbsp;</font><font color="#000000">{</font><br /><font color="#ffffff">&nbsp;&nbsp;&nbsp; </font><font color="#000000">CoffeeMachine&nbsp;machine&nbsp;=&nbsp;</font><font color="#7f0055"><strong>new&nbsp;</strong></font><font color="#000000">CoffeeMachine</font><font color="#000000">()</font><font color="#000000">;</font><br /><br /><font color="#ffffff">&nbsp;&nbsp;&nbsp; </font><font color="#000000">Class&nbsp;machineClass&nbsp;=&nbsp;machine.getClass</font><font color="#000000">()</font><font color="#000000">;</font><br /><font color="#ffffff">&nbsp;&nbsp;&nbsp; </font><font color="#000000">Method&nbsp;method&nbsp;=&nbsp;machineClass.getDeclaredMethod</font><font color="#000000">(</font><font color="#2a00ff">&quot;getSecretIngredient&quot;</font><font color="#000000">)</font><font color="#000000">;</font><br /><font color="#ffffff">&nbsp;&nbsp;&nbsp; </font><font color="#000000">method.setAccessible</font><font color="#000000">(</font><font color="#7f0055"><strong>true</strong></font><font color="#000000">)</font><font color="#000000">;</font><br /><font color="#ffffff">&nbsp;&nbsp;&nbsp; </font><font color="#000000">Object&nbsp;result&nbsp;=&nbsp;method.invoke</font><font color="#000000">(</font><font color="#000000">machine</font><font color="#000000">)</font><font color="#000000">;</font><br /><font color="#ffffff">&nbsp;&nbsp;&nbsp; </font><br /><font color="#ffffff">&nbsp;&nbsp;&nbsp; </font><font color="#000000">System.out.println</font><font color="#000000">(</font><font color="#000000">result.getClass</font><font color="#000000">())</font><font color="#000000">;</font><br /><font color="#000000">}</font></code> <br />

First I created a CoffeeMachine object and invoked the getClass() method on it. When you analyze this object in the debugger you get lots of interesting information:

In the declaredMethods field you can see there are two methods. The getSecretIngredient() and getCoffee() method. The getCoffee methods is testable, but we’re interested in the secret ingredient!
Create a Method object to get the method (it took me some time to grasp that a Method is an Object)
Now the magic method I was looking for: setAccessible. This method makes the getSecretIngredient public so we can invoke it.
Because the compiler doesn’t allow us to do a machine.getSecretIngredient(); (which is now technically possible) we have to invoke the method via the invoke method. You have to pass the object on which you want to invoke the method and you’re ready to go.

Unfortunately I’m now allow to tell you what secret ingredient the AMIS Coffee Machine uses so you have to believe me that the unit test returns the right Object.

Testing with parameters

It’s also possible to pass parameters to the methods. Assume we have a Long and Integer parameter on our getSecretIngredient method.

The getDeclaredMethod method then looks like this:

<code><br /><font color="#000000">Method&nbsp;method=machineClass.getDeclaredMethod</font><font color="#000000">(</font><font color="#2a00ff">&quot;getSecretIngredient&quot;</font><font color="#000000">,&nbsp;</font><font color="#7f0055"><strong>new&nbsp;</strong></font><font color="#000000">Class</font><font color="#000000">[]{</font><font color="#000000">Long.class,&nbsp;Integer.</font><font color="#7f0055"><strong>class</strong></font><font color="#000000">})</font><font color="#000000">;</font></code>

and the invoke:

<code><br /><font color="#000000">Object&nbsp;result=method.invoke</font><font color="#000000">(</font><font color="#000000">machine,&nbsp;</font><font color="#990000">10L</font><font color="#000000">,&nbsp;</font><font color="#990000">20</font><font color="#000000">)</font><font color="#000000">;</font></code>

Conclusion

Fortunately the Reflection API is very slow, maybe Sun did this on purpose. Think of the evil things you can do with this API. You can screw up refactorings and access methods that were hidden on purpose. So promise me you won’t tell this to junior developers, they will use this API in production code and break all the encapsulation you introduced to protect themselves.

When you want to know more about testing private methods I recommend this article.

Share.

About Author

2 Comments

  1. You can access private method this way, but you shouldn’t need to unit test them. They should be hidden implementation details which you should be able to replace or remove without breaking your unit tests.
    If you really must test them, you can make them package private and give your unit tests the same package. (in another base directory)

    You can do alot worse with reflections. You can set final fields and static finals. You can get the “defineClass” method on your class loader and inject classes which don’t appear on disk.
    If you want really dangerous, try the sun.misc.Unsafe class which lets you do alot of the things Java was supposed to prevent you from doing. :)