Annotation to Make a Private Method Public Only for Test Classes

How do I test a class that has private methods, fields or inner classes?

If you have somewhat of a legacy Java application, and you're not allowed to change the visibility of your methods, the best way to test private methods is to use reflection.

Internally we're using helpers to get/set private and private static variables as well as invoke private and private static methods. The following patterns will let you do pretty much anything related to the private methods and fields. Of course, you can't change private static final variables through reflection.

Method method = TargetClass.getDeclaredMethod(methodName, argClasses);
method.setAccessible(true);
return method.invoke(targetObject, argObjects);

And for fields:

Field field = TargetClass.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);


Notes:

  1. TargetClass.getDeclaredMethod(methodName, argClasses) lets you look into private methods. The same thing applies for
    getDeclaredField.
  2. The setAccessible(true) is required to play around with privates.

How do I test a class that has private methods, fields or inner classes?

If you have somewhat of a legacy Java application, and you're not allowed to change the visibility of your methods, the best way to test private methods is to use reflection.

Internally we're using helpers to get/set private and private static variables as well as invoke private and private static methods. The following patterns will let you do pretty much anything related to the private methods and fields. Of course, you can't change private static final variables through reflection.

Method method = TargetClass.getDeclaredMethod(methodName, argClasses);
method.setAccessible(true);
return method.invoke(targetObject, argObjects);

And for fields:

Field field = TargetClass.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);


Notes:

  1. TargetClass.getDeclaredMethod(methodName, argClasses) lets you look into private methods. The same thing applies for
    getDeclaredField.
  2. The setAccessible(true) is required to play around with privates.

Should I test private methods or only public ones?

I do not unit test private methods. A private method is an implementation detail that should be hidden to the users of the class. Testing private methods breaks encapsulation.

If I find that the private method is huge or complex or important enough to require its own tests, I just put it in another class and make it public there (Method Object). Then I can easily test the previously-private-but-now-public method that now lives on its own class.

Is it OK to make a private method to non-private just because to make it easier for testing?

I think this is something pretty personal. In most of the scenarios I don't see any harm on changing the scope of a method from private to protected/package private if this helps to write better unit tests. It not only allows to invoke the method from your test - which you might do either way using reflection -, but also to override/mock that method when testing another one invoking this.

However, if you don't want to do it you still can use tools like Spring's ReflectionTestUtils to make the invocation of private methods less painful.

How to use VisibleForTesting for pure JUnit tests

The Tag itself helps with the linter to identify unwanted access.

To lower the risk of use it directly, add this methods as internal in Kotlin or protected in Java instead of public and with that only the tests or classes that are in the same package will be able to access that method.

Java:

@VisibleForTesting
protected Address address() {
return mAddress;
}

Kotlin:

@VisibleForTesting
internal fun address(): Address {
return address;
}

Unit testing of private methods

If the methods are complex enough to warrant testing in isolation, then refactor them into their own class(es) and test via their public interface(s). Then use them privately in the original class.



Related Topics



Leave a reply



Submit