org.junit.runners.model.TestClass Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of junit Show documentation
Show all versions of junit Show documentation
JUnit is a unit testing framework for Java, created by Erich Gamma and Kent Beck.
package org.junit.runners.model;
import static java.lang.reflect.Modifier.isStatic;
import static org.junit.internal.MethodSorter.NAME_ASCENDING;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.internal.MethodSorter;
/**
* Wraps a class to be run, providing method validation and annotation searching
*
* @since 4.5
*/
public class TestClass implements Annotatable {
private static final FieldComparator FIELD_COMPARATOR = new FieldComparator();
private static final MethodComparator METHOD_COMPARATOR = new MethodComparator();
private final Class clazz;
private final Map, List> methodsForAnnotations;
private final Map, List> fieldsForAnnotations;
/**
* Creates a {@code TestClass} wrapping {@code clazz}. Each time this
* constructor executes, the class is scanned for annotations, which can be
* an expensive process (we hope in future JDK's it will not be.) Therefore,
* try to share instances of {@code TestClass} where possible.
*/
public TestClass(Class clazz) {
this.clazz = clazz;
if (clazz != null && clazz.getConstructors().length > 1) {
throw new IllegalArgumentException(
"Test class can only have one constructor");
}
Map, List> methodsForAnnotations =
new LinkedHashMap, List>();
Map, List> fieldsForAnnotations =
new LinkedHashMap, List>();
scanAnnotatedMembers(methodsForAnnotations, fieldsForAnnotations);
this.methodsForAnnotations = makeDeeplyUnmodifiable(methodsForAnnotations);
this.fieldsForAnnotations = makeDeeplyUnmodifiable(fieldsForAnnotations);
}
protected void scanAnnotatedMembers(Map, List> methodsForAnnotations, Map, List> fieldsForAnnotations) {
for (Class eachClass : getSuperClasses(clazz)) {
for (Method eachMethod : MethodSorter.getDeclaredMethods(eachClass)) {
addToAnnotationLists(new FrameworkMethod(eachMethod), methodsForAnnotations);
}
// ensuring fields are sorted to make sure that entries are inserted
// and read from fieldForAnnotations in a deterministic order
for (Field eachField : getSortedDeclaredFields(eachClass)) {
addToAnnotationLists(new FrameworkField(eachField), fieldsForAnnotations);
}
}
}
private static Field[] getSortedDeclaredFields(Class clazz) {
Field[] declaredFields = clazz.getDeclaredFields();
Arrays.sort(declaredFields, FIELD_COMPARATOR);
return declaredFields;
}
protected static > void addToAnnotationLists(T member,
Map, List> map) {
for (Annotation each : member.getAnnotations()) {
Class type = each.annotationType();
List members = getAnnotatedMembers(map, type, true);
T memberToAdd = member.handlePossibleBridgeMethod(members);
if (memberToAdd == null) {
return;
}
if (runsTopToBottom(type)) {
members.add(0, memberToAdd);
} else {
members.add(memberToAdd);
}
}
}
private static > Map, List>
makeDeeplyUnmodifiable(Map, List> source) {
Map, List> copy =
new LinkedHashMap, List>();
for (Map.Entry, List> entry : source.entrySet()) {
copy.put(entry.getKey(), Collections.unmodifiableList(entry.getValue()));
}
return Collections.unmodifiableMap(copy);
}
/**
* Returns, efficiently, all the non-overridden methods in this class and
* its superclasses that are annotated}.
*
* @since 4.12
*/
public List getAnnotatedMethods() {
List methods = collectValues(methodsForAnnotations);
Collections.sort(methods, METHOD_COMPARATOR);
return methods;
}
/**
* Returns, efficiently, all the non-overridden methods in this class and
* its superclasses that are annotated with {@code annotationClass}.
*/
public List getAnnotatedMethods(
Class annotationClass) {
return Collections.unmodifiableList(getAnnotatedMembers(methodsForAnnotations, annotationClass, false));
}
/**
* Returns, efficiently, all the non-overridden fields in this class and its
* superclasses that are annotated.
*
* @since 4.12
*/
public List getAnnotatedFields() {
return collectValues(fieldsForAnnotations);
}
/**
* Returns, efficiently, all the non-overridden fields in this class and its
* superclasses that are annotated with {@code annotationClass}.
*/
public List getAnnotatedFields(
Class annotationClass) {
return Collections.unmodifiableList(getAnnotatedMembers(fieldsForAnnotations, annotationClass, false));
}
private List collectValues(Map> map) {
Set values = new LinkedHashSet();
for (List additionalValues : map.values()) {
values.addAll(additionalValues);
}
return new ArrayList(values);
}
private static List getAnnotatedMembers(Map, List> map,
Class type, boolean fillIfAbsent) {
if (!map.containsKey(type) && fillIfAbsent) {
map.put(type, new ArrayList());
}
List members = map.get(type);
return members == null ? Collections.emptyList() : members;
}
private static boolean runsTopToBottom(Class annotation) {
return annotation.equals(Before.class)
|| annotation.equals(BeforeClass.class);
}
private static List> getSuperClasses(Class testClass) {
List> results = new ArrayList>();
Class current = testClass;
while (current != null) {
results.add(current);
current = current.getSuperclass();
}
return results;
}
/**
* Returns the underlying Java class.
*/
public Class getJavaClass() {
return clazz;
}
/**
* Returns the class's name.
*/
public String getName() {
if (clazz == null) {
return "null";
}
return clazz.getName();
}
/**
* Returns the only public constructor in the class, or throws an {@code
* AssertionError} if there are more or less than one.
*/
public Constructor getOnlyConstructor() {
Constructor[] constructors = clazz.getConstructors();
Assert.assertEquals(1, constructors.length);
return constructors[0];
}
/**
* Returns the annotations on this class
*/
public Annotation[] getAnnotations() {
if (clazz == null) {
return new Annotation[0];
}
return clazz.getAnnotations();
}
public T getAnnotation(Class annotationType) {
if (clazz == null) {
return null;
}
return clazz.getAnnotation(annotationType);
}
public List getAnnotatedFieldValues(Object test,
Class annotationClass, Class valueClass) {
final List results = new ArrayList();
collectAnnotatedFieldValues(test, annotationClass, valueClass,
new MemberValueConsumer() {
public void accept(FrameworkMember member, T value) {
results.add(value);
}
});
return results;
}
/**
* Finds the fields annotated with the specified annotation and having the specified type,
* retrieves the values and passes those to the specified consumer.
*
* @since 4.13
*/
public void collectAnnotatedFieldValues(Object test,
Class annotationClass, Class valueClass,
MemberValueConsumer consumer) {
for (FrameworkField each : getAnnotatedFields(annotationClass)) {
try {
Object fieldValue = each.get(test);
if (valueClass.isInstance(fieldValue)) {
consumer.accept(each, valueClass.cast(fieldValue));
}
} catch (IllegalAccessException e) {
throw new RuntimeException(
"How did getFields return a field we couldn't access?", e);
}
}
}
public List getAnnotatedMethodValues(Object test,
Class annotationClass, Class valueClass) {
final List results = new ArrayList();
collectAnnotatedMethodValues(test, annotationClass, valueClass,
new MemberValueConsumer() {
public void accept(FrameworkMember member, T value) {
results.add(value);
}
});
return results;
}
/**
* Finds the methods annotated with the specified annotation and returning the specified type,
* invokes it and pass the return value to the specified consumer.
*
* @since 4.13
*/
public void collectAnnotatedMethodValues(Object test,
Class annotationClass, Class valueClass,
MemberValueConsumer consumer) {
for (FrameworkMethod each : getAnnotatedMethods(annotationClass)) {
try {
/*
* A method annotated with @Rule may return a @TestRule or a @MethodRule,
* we cannot call the method to check whether the return type matches our
* expectation i.e. subclass of valueClass. If we do that then the method
* will be invoked twice and we do not want to do that. So we first check
* whether return type matches our expectation and only then call the method
* to fetch the MethodRule
*/
if (valueClass.isAssignableFrom(each.getReturnType())) {
Object fieldValue = each.invokeExplosively(test);
consumer.accept(each, valueClass.cast(fieldValue));
}
} catch (Throwable e) {
throw new RuntimeException(
"Exception in " + each.getName(), e);
}
}
}
public boolean isPublic() {
return Modifier.isPublic(clazz.getModifiers());
}
public boolean isANonStaticInnerClass() {
return clazz.isMemberClass() && !isStatic(clazz.getModifiers());
}
@Override
public int hashCode() {
return (clazz == null) ? 0 : clazz.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
TestClass other = (TestClass) obj;
return clazz == other.clazz;
}
/**
* Compares two fields by its name.
*/
private static class FieldComparator implements Comparator {
public int compare(Field left, Field right) {
return left.getName().compareTo(right.getName());
}
}
/**
* Compares two methods by its name.
*/
private static class MethodComparator implements
Comparator {
public int compare(FrameworkMethod left, FrameworkMethod right) {
return NAME_ASCENDING.compare(left.getMethod(), right.getMethod());
}
}
}