Powermock is an open-source Java framework. Powermock leverages the capabilities of frameworks like Mockito to create mock objects in unit testing. Unit testing private methods and classes are possible by using Powermock's Whitebox. The Whitebox uses reflection to create inner class instances and invoke private methods outside the class file they are declared.
If you have been researching how to unit test private methods and classes, you may have come across various debates about best practices. I am confident that "Don't unit test private methods" is not the answer you want to find. If unit testing private methods and classes are inevitable for your situation, this article explains how to test private methods and classes.
org.powermock.reflect.Whitebox provides various utilities for accessing the internals of a class. These utilities include instantiating private classes and invoking private methods by using reflection. Whitebox is a utility intended for tests.
The below is the test candidate class with private methods and an inner class. Note that those methods and inner class are not accessible outside the class they are defined.
package com.learnbestcoding.junit;
import java.util.ArrayList;
import java.util.List;
public class Util {
public void runMethods(String[] args) {
//Public method uses all private methods
}
private static int getNumber() {
return 1;
}
private List<String> getLanguages() {
List languages = new ArrayList<String>();
languages.add("Java");
languages.add("Sql");
languages.add("Javascript");
return languages;
}
private void addLanguage(List languages, String language) {
languages.add(language);
}
private ProgrammingLanguages getJavaLanguageClass() {
ProgrammingLanguages languages = new ProgrammingLanguages();
languages.setLanguageId(1);
languages.setLanguageName("Java");
languages.setNoOfEnrolments(100);
return languages;
}
private ProgrammingLanguages createLanguageClass(int languageId, String languageName, int noOfEnrolments) {
ProgrammingLanguages languages = new ProgrammingLanguages();
languages.setLanguageId(languageId);
languages.setLanguageName(languageName);
languages.setNoOfEnrolments(noOfEnrolments);
return languages;
}
private List<ProgrammingLanguages> addLanguageClass(ProgrammingLanguages language) {
List<ProgrammingLanguages> languageList = new ArrayList<ProgrammingLanguages>();
languageList.add(language);
return languageList;
}
class ProgrammingLanguages {
int languageId;
String languageName;
int noOfEnrolments;
public int getLanguageId() {
return languageId;
}
public void setLanguageId(int languageId) {
this.languageId = languageId;
}
public String getLanguageName() {
return languageName;
}
public void setLanguageName(String languageName) {
this.languageName = languageName;
}
public int getNoOfEnrolments() {
return noOfEnrolments;
}
public void setNoOfEnrolments(int noOfEnrolments) {
this.noOfEnrolments = noOfEnrolments;
}
}
}
Whitebox is part of powermock. Therefore, we need Junit and Powermock dependencies in the pom.xml file.
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.learnbestcoding</groupId>
<artifactId>junit</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>junit</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>1.7.4</version>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>1.7.4</version>
</dependency>
</dependencies>
</project>
In this tutorial, we will discuss six different scenarios to test private classes and methods. These scenarios include simple private methods with no arguments to private methods that accept private class arguments and return private classes.
The private method
package com.learnbestcoding.junit;
import static org.junit.Assert.assertEquals;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.reflect.Whitebox;
@RunWith(PowerMockRunner.class)
public class UtilTest {
@Test
public void test_getNumber() throws Exception {
int obj = Whitebox.invokeMethod(new Util(),"getNumber");
assertEquals(obj, 1);
}
}
The private method
@Test
public void test_getLanguages() throws Exception {
List obj = Whitebox.invokeMethod(new Util(),"getLanguages");
assertEquals(obj.get(0), "Java");
}
The private method
@Test
public void test_addLanguage() throws Exception {
List languages = new ArrayList<String>();
languages.add("Java");
languages.add("Sql");
languages.add("Javascript");
Whitebox.invokeMethod(new Util(),"addLanguage",languages,"Python");
assertEquals(languages.get(3), "Python");
}
The private method
@Test
public void test_getLanguageClass() throws Exception {
Object obj =Whitebox.invokeMethod(new Util(),"getJavaLanguageClass");
assertEquals(obj.getClass().getDeclaredField("languageId").get(obj), 1);
assertEquals(obj.getClass().getDeclaredField("languageName").get(obj), "Java");
assertEquals(obj.getClass().getDeclaredField("noOfEnrolments").get(obj), 100);
}
The private method
@Test
public void test_createLanguageClass() throws Exception {
Object obj =Whitebox.invokeMethod(new Util(),"createLanguageClass",2,"Sql",240);
assertEquals(obj.getClass().getDeclaredField("languageId").get(obj), 2);
assertEquals(obj.getClass().getDeclaredField("languageName").get(obj), "Sql");
assertEquals(obj.getClass().getDeclaredField("noOfEnrolments").get(obj), 240);
}
The private method
@Test
public void test_addLanguageClass() throws Exception {
Object programmingLanguages = new Object();
Class clazz = Whitebox.getInnerClassType(Util.class, "ProgrammingLanguages");
Constructor constructor = Whitebox.getConstructor(clazz,Util.class);
programmingLanguages = constructor.newInstance(new Util());
Field field = null;
field = programmingLanguages.getClass().getDeclaredField("languageId");
field.setAccessible(true);
field.set(programmingLanguages, 1);
field = programmingLanguages.getClass().getDeclaredField("languageName");
field.setAccessible(true);
field.set(programmingLanguages, "Java");
field = programmingLanguages.getClass().getDeclaredField("noOfEnrolments");
field.setAccessible(true);
field.set(programmingLanguages, 100);
List obj = Whitebox.invokeMethod(new Util(),"addLanguageClass",programmingLanguages);
assertEquals(obj.get(0).getClass().getDeclaredField("languageId").get(obj.get(0)), 1);
assertEquals(obj.get(0).getClass().getDeclaredField("languageName").get(obj.get(0)), "Java");
assertEquals(obj.get(0).getClass().getDeclaredField("noOfEnrolments").get(obj.get(0)), 100);
}
package com.learnbestcoding.junit;
import static org.junit.Assert.assertEquals;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.reflect.Whitebox;
@RunWith(PowerMockRunner.class)
public class UtilTest {
@Test
public void test_getNumber() throws Exception {
int obj = Whitebox.invokeMethod(new Util(),"getNumber");
assertEquals(obj, 1);
}
@Test
public void test_getLanguages() throws Exception {
List obj = Whitebox.invokeMethod(new Util(),"getLanguages");
assertEquals(obj.get(0), "Java");
}
@Test
public void test_addLanguage() throws Exception {
List languages = new ArrayList<String>();
languages.add("Java");
languages.add("Sql");
languages.add("Javascript");
Whitebox.invokeMethod(new Util(),"addLanguage",languages,"Python");
assertEquals(languages.get(3), "Python");
}
@Test
public void test_getLanguageClass() throws Exception {
Object obj =Whitebox.invokeMethod(new Util(),"getJavaLanguageClass");
assertEquals(obj.getClass().getDeclaredField("languageId").get(obj), 1);
assertEquals(obj.getClass().getDeclaredField("languageName").get(obj), "Java");
assertEquals(obj.getClass().getDeclaredField("noOfEnrolments").get(obj), 100);
}
@Test
public void test_createLanguageClass() throws Exception {
Object obj =Whitebox.invokeMethod(new Util(),"createLanguageClass",2,"Sql",240);
assertEquals(obj.getClass().getDeclaredField("languageId").get(obj), 2);
assertEquals(obj.getClass().getDeclaredField("languageName").get(obj), "Sql");
assertEquals(obj.getClass().getDeclaredField("noOfEnrolments").get(obj), 240);
}
@Test
public void test_addLanguageClass() throws Exception {
Object programmingLanguages = new Object();
Class clazz = Whitebox.getInnerClassType(Util.class, "ProgrammingLanguages");
Constructor constructor = Whitebox.getConstructor(clazz,Util.class);
programmingLanguages = constructor.newInstance(new Util());
Field field = null;
field = programmingLanguages.getClass().getDeclaredField("languageId");
field.setAccessible(true);
field.set(programmingLanguages, 1);
field = programmingLanguages.getClass().getDeclaredField("languageName");
field.setAccessible(true);
field.set(programmingLanguages, "Java");
field = programmingLanguages.getClass().getDeclaredField("noOfEnrolments");
field.setAccessible(true);
field.set(programmingLanguages, 100);
List obj = Whitebox.invokeMethod(new Util(),"addLanguageClass",programmingLanguages);
assertEquals(obj.get(0).getClass().getDeclaredField("languageId").get(obj.get(0)), 1);
assertEquals(obj.get(0).getClass().getDeclaredField("languageName").get(obj.get(0)), "Java");
assertEquals(obj.get(0).getClass().getDeclaredField("noOfEnrolments").get(obj.get(0)), 100);
}
}
Powermock offers Whitebox utility offers utilities to unit test private methods and private classes using reflection. Whitebox makes private methods and classes accessible outside their defined scope and visibility.