Wednesday, May 20, 2009

Testing private methods using Reflection

- What about testing private methods ?
- Hum... You can't because they're private dummy.
- And what about using reflection to make them accessible ?

Because we don't have access to private methods, we generally don't test them. This can be a weakness in your testing strategy. Private methods are usually a very sensible part of your code. I've have seen a lot of developers modifying the visibility of their code from private to protected. That's a bad practice. Don't change the visibility of your code for testing purposes.

A solution consists of using reflection to make these private methods accessible for testing.

I really don't like to use reflexion in my application code because if someone is doing some refactoring like renaming a method and forget to update the reflexive code part, you will get a very bad runtime exception. If it is possible to perform an operation without using reflection, then it is preferable to avoid using it.

Test code is not application code. Your tests does not go into production, that's why I'm not afraid of using reflection in my test classes.

Here's an example :

MyClass.java

public class MyClass {

private String myPrivateMethod(Long id) {
//Do something private
return "SomeString_" + id;
}
}

MyClassTest.java

import java.lang.reflect.Method;

import static org.junit.Assert.*;
import org.junit.Test;

public class MyClassTest {

private MyClass underTest;

@Test
public void testMyPrivateMethod() throws Exception {

underTest = new MyClass();

Class[] parameterTypes = new Class[1];
parameterTypes[0] = java.lang.Long.class;

Method m = underTest.getClass().getDeclaredMethod("myPrivateMethod", parameterTypes);
m.setAccessible(true);

Object[] parameters = new Object[1];
parameters[0] = 5569L;

String result = (String) m.invoke(underTest, parameters);

//Do your assertions
assertNotNull(result);
}
}

17 comments:

  1. You should not even be tempted into testing private methods. Private methods should be private because they are considered internal workings of a class.
    If you find yourself wanting to test these, it's because you are trying to test how your code works, rather than its actual goal.

    ReplyDelete
  2. have to agree unit testing is not necessarily testing every method, but a unit of work.

    ReplyDelete
  3. I think it is a valid approach! Thanks for this!
    The programming world is not perfect and so not all programmers are perfect in splitting code into testable units.

    Another simpler solution (only for me!) is to make the private methods package protected ...

    ReplyDelete
  4. Well I knew when writing this article that a lot of people will disagree.

    But one day I found myself in a weird situation because something went wrong in one of my private method because someone made a little modification to it. I was testing the goal of the public method using it and not the limits of my private method so the tests didn't actually shows anything. And guess what happens some days later, phones ringing and people screaming because our application was down...

    ReplyDelete
  5. Private methods are supporting methods to the exposed API. Using proper code coverage (Cobertura, Clover, Emma, etc), you can verify that you're testing all branches in private methods.

    If you cannot test the exposed API and cover all branches in a private method, then you probably need to refactor, or you're not using enough test data. You shouldn't just test the "happy path." There are many edge cases and boundary conditions that need to be tested as well.

    I've had people on my team insist on making private methods as protected (or default). There have been many debates on this issue.

    ReplyDelete
  6. A private method call generator: http://code.google.com/p/hexacta-booster/
    Enjoy!

    ReplyDelete
  7. Challenges to test private methods can also be smell of bad design. By keeping Single Responsibility Principle in mind and by keeping method implementation on single abstraction level one can spot functionality that can be delegated to some other class to be tested separately through public api.

    ReplyDelete
  8. Greate example
    Simple and clear

    ReplyDelete
  9. I tried as in this example and works fine.
    I then tried a simillar example, instead of a Long parameter replaced with an Array of File objects.
    and the line written as
    parameterTypes[0] = java.util.Arrays.class;
    and now when i try creating the Method object it fails.

    could you please tell me what I am doing wrong.

    ReplyDelete
  10. I disagree with those who say it's bad to test private methods. There is no one size fits all rule. Sometimes a private method is complex and worth testing alone.

    Sometimes a very complex object is passed into a public api, but you are only interested in testing a portion of the functionality.

    ReplyDelete
  11. From the extreme-programming point of view (and my own) private methods should be tested. If I'm writing test cases before writing methods themselves, then to write a private method I first need to write a test for it. I'll keep testing my private methods. (But for convenience's sake, I may make some or all of them public.)

    ReplyDelete
  12. In JUnit never forget to extend the TestCase class while writing your tescases. otherwise exceptions will be thrown.

    ReplyDelete
  13. That is the old way Garima. No need to extend the TestClass class with Junit4 and the @Test annotation. Also there's no need to start your your method name with test.

    ReplyDelete
  14. I don't like the reflection API for the same reasons you mentioned, but www.dp4j.com should solve all that still using the reflection API; What changes is that you write plain old good direct members access, and dp4j converts that into the reflection calls (roughly 4 lines per line) directly into the AST.

    ReplyDelete
    Replies
    1. I too, but how use dp4j with Eclipse?

      Delete
    2. It doesn't really work with Eclipse, unless you use the official Java Compiler.

      http://dp4j.com/faq#TOC-Does-it-work-with-Eclipse-

      Delete
  15. In Java 1.6 and 1.7 you can write:

    Method m = underTest.getClass().getDeclaredMethod("myPrivateMethod", long.class);
    m.setAccessible(true);

    String result = (String) m.invoke(underTest, 5569L);

    //Do your assertions
    assertNotNull(result);

    ReplyDelete