问题
I am writing a test case using JUnit
and the method under test takes a final class with a private constructor as a parameter. Since I cannot instantiate it with the new
keyword I tried using Mockito
but found out that Mockito
doesn't like final class
. I went to use PowerMockito
which seemed reasonable to me but PowerMockito.mockStatic(Field.class);
is a void method and I need a reference of Field
so that I can pass it as an argument while invoking the method.
I want to catch IllegalArgumentException
but first I need to pass reference of Field
as an argument
Method under test
public boolean accept(Field field) {
if( ignoreNulls ) {
try {
if( field.get( super.getObject() ) == null ) {
return false;
}
} catch (IllegalArgumentException e) {
} catch (IllegalAccessException e) {
}
}
return super.accept(field);
}
JUnit test case
@Test(expected=IllegalArgumentException.class)
public void testAccept() throws Exception {
DefaultToStringBuilder builder = new DefaultToStringBuilder(new Object());
PowerMockito.mockStatic(Field.class);
builder.accept(?);
}
I am not sure how should I be doing this.
Thanks in advance
回答1:
My answer don't do that. Do not engage in PowerMock just because your production code can't be tested otherwise.
You will find out soon that PowerMock can create more problems than it solves.
Typically, the need to use PowerMock comes from a broken design. So, instead of spending hours to enable a broken design for testing via PowerMock ... you better spent a fraction of that time in order to rework your design. (and from my one experience: PowerMock can quickly lead to countless hours being spent on it)
Meaning: you could add a package protected constructor for testing purposes. Or you might go one step further to get the broader picture; and find ways that allow for a public constructor; whilst maintaining the design ideas that lead to the current final/private implementation.
回答2:
We can actually use Core Java
to achieve this. Code below shows how to do it.
private Field field;
@Test(expected=IllegalArgumentException.class)
public void testAccept() throws Exception {
Class<?> clazz = Field.class;
Constructor<?> [] constructors = clazz.getDeclaredConstructors();
for(Constructor cons: constructors) {
cons.setAccessible(true);
field = (Field) cons.newInstance();
}
DefaultToStringBuilder builder = new DefaultToStringBuilder(new Object());
builder.accept(field);
assertNotNull(builder);
}
回答3:
Use java reflection to get object for private construtor from outside class. Here is example.
//Sample class Student.java
public class Student {
private Integer sudentId;
private String studentName;
private Student(){}
private Student(Integer studentId, String studentName) {
this.studentId = studentId;
this.studentName = studentName;
}
public Integer getStudentId() {
return studentId;
}
public String getStudentName() {
return studentName;
}
}
In below code, There are two ways to instantiate the class
1- Find the private constructor using given constructor name and
instantiate the class.
2- Find the private constructor for given number of arguments and
types and instantiate the class
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
public class PrivateConstructorDemo {
//Find the private constructor using given constructor name and instantiate the class.
public void createObjectByConstructorName(int id, String name) throws NoSuchMethodException, SecurityException,
InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{
Constructor<Student> constructor = Student.class.getDeclaredConstructor(Integer.class, String.class);
if (Modifier.isPrivate(constructor.getModifiers())) {
constructor.setAccessible(true);
Student student = (Student)constructor.newInstance(id, name);
System.out.println("Student Id:"+ student.getStudentId());
System.out.println("Student Name:"+ student.getStudentName());
}
}
//For given number of arguments and types and instantiate the class.
public void createObject(int id, String name) throws InstantiationException,
IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Constructor<?>[] constructors = Student.class.getDeclaredConstructors();
for (Constructor<?> constructor : constructors) {
if (Modifier.isPrivate(constructor.getModifiers())) {
constructor.setAccessible(true);
Class<?>[] clazzs = constructor.getParameterTypes();
if (constructor.getParameterCount() == 2 && clazzs[0] == Integer.class &&
clazzs[1] == String.class) {
Object ob = constructor.newInstance(id, name);
if (ob instanceof Student) {
Student student = (Student)ob;
System.out.println("Student Id:"+ student.getStudentId());
System.out.println("Student Name:"+ student.getStudentName());
}
}
}
}
}
public static void main(String[] args) throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
PrivateConstructorDemo obj = new PrivateConstructorDemo();
obj.createObject(10, "Sandeep");
System.out.println("-------------------------");
obj.createObjectByConstructorName(20,"Sandeep");
}
}
来源:https://stackoverflow.com/questions/37447400/instantiating-a-class-with-private-constructor-using-java-mockito-powermockito