Just in case you really have to test private methods in JUnit. I forget how to implement it on a regular basis, so I'll never forget it if I write it firmly once. I will write it down with the motive.
Consider a class with a private method like the one below.
public class MyClass {
private String introduceMyself(String name){
return "my name is " + name + ".";
}
}
Testing of MyClass's private introduceMyself method can be done using reflection as follows:
import com.example.MyClass;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Method;
import static org.assertj.core.api.Assertions.assertThat;
public class MyClassTest {
@Test
void testIntroduceMyself01() throws Exception {
MyClass targetClass = new MyClass();
//Get information about the method under test
Method method = MyClass.class.getDeclaredMethod("introduceMyself", String.class);
//Remove access restrictions on methods
method.setAccessible(true);
//Method call
String selfIntroduction = (String) method.invoke(targetClass,"John");
//Assert the result
assertThat(selfIntroduction).isEqualTo("my name is John.");
}
}
The above is fine for normal test cases, but be careful when exceptions are thrown from private methods.
Exceptions that occur in Method # invoke will be wrapped in the InvocationTargetException class. Therefore, it is necessary to make an assertion after extracting the cause exception.
public class MyClass {
private String introduceMyself(String name){
//Throws an exception if the argument is an empty string
if(name.isEmpty()){
throw new IllegalArgumentException("argument must not be empty.");
}
return "my name is " + name + ".";
}
}
public class MyClassTest {
@Test
void testIntroduceMyself02() throws Exception {
MyClass targetClass = new MyClass();
Method method = MyClass.class.getDeclaredMethod("introduceMyself", String.class);
method.setAccessible(true);
try {
method.invoke(targetClass, "");
fail("this code should not has been reached.");
}catch (InvocationTargetException e) {
//Assertion after fetching the cause exception with getCause
assertThat(e.getCause()).isInstanceOf(IllegalArgumentException.class);
}
}
}
If you want to pass null as an argument of the method to be called by reflection, you need to pass an Object array that has null as an element, so be careful here as well.
method.invoke(targetClass, new Object[]{null});
//This is no good
method.invoke(targetClass, null);
As I said so far, private methods should not be unit tested in principle.
A private method is, so to speak, an internal process cut out from a public (protected) method. By forcibly testing internal processing with reflection, the test code becomes dependent on the internal implementation, which becomes a barrier when refactoring or adding functions.
Comprehensiveness can be tested via the calling method if it is a well-designed private method. If there is a branch that must be called individually for private methods, it is effectively dead code and you should review your implementation.
--Reflection required to call private method --To test the exception, getCause from InvocationTargetException. --When passing null to invoke, pass an Object array with null as an element --If you need to test a private method, revisit your design --In principle, private methods should be tested via the calling method.
Recommended Posts