package org.junit.runners.parameterized; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.util.List; import org.junit.internal.runners.statements.RunAfters; import org.junit.internal.runners.statements.RunBefores; import org.junit.runner.RunWith; import org.junit.runner.notification.RunNotifier; import org.junit.runners.BlockJUnit4ClassRunner; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameter; import org.junit.runners.model.FrameworkField; import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.InitializationError; import org.junit.runners.model.Statement; /** * A {@link BlockJUnit4ClassRunner} with parameters support. Parameters can be * injected via constructor or into annotated fields. */ public class BlockJUnit4ClassRunnerWithParameters extends BlockJUnit4ClassRunner { private enum InjectionType { CONSTRUCTOR, FIELD } private final Object[] parameters; private final String name; public BlockJUnit4ClassRunnerWithParameters(TestWithParameters test) throws InitializationError { super(test.getTestClass().getJavaClass()); parameters = test.getParameters().toArray( new Object[test.getParameters().size()]); name = test.getName(); } @Override public Object createTest() throws Exception { InjectionType injectionType = getInjectionType(); switch (injectionType) { case CONSTRUCTOR: return createTestUsingConstructorInjection(); case FIELD: return createTestUsingFieldInjection(); default: throw new IllegalStateException("The injection type " + injectionType + " is not supported."); } } private Object createTestUsingConstructorInjection() throws Exception { return getTestClass().getOnlyConstructor().newInstance(parameters); } private Object createTestUsingFieldInjection() throws Exception { List<FrameworkField> annotatedFieldsByParameter = getAnnotatedFieldsByParameter(); if (annotatedFieldsByParameter.size() != parameters.length) { throw new Exception( "Wrong number of parameters and @Parameter fields." + " @Parameter fields counted: " + annotatedFieldsByParameter.size() + ", available parameters: " + parameters.length + "."); } Object testClassInstance = getTestClass().getJavaClass().newInstance(); for (FrameworkField each : annotatedFieldsByParameter) { Field field = each.getField(); Parameter annotation = field.getAnnotation(Parameter.class); int index = annotation.value(); try { field.set(testClassInstance, parameters[index]); } catch (IllegalArgumentException iare) { throw new Exception(getTestClass().getName() + ": Trying to set " + field.getName() + " with the value " + parameters[index] + " that is not the right type (" + parameters[index].getClass().getSimpleName() + " instead of " + field.getType().getSimpleName() + ").", iare); } } return testClassInstance; } @Override protected String getName() { return name; } @Override protected String testName(FrameworkMethod method) { return method.getName() + getName(); } @Override protected void validateConstructor(List<Throwable> errors) { validateOnlyOneConstructor(errors); if (getInjectionType() != InjectionType.CONSTRUCTOR) { validateZeroArgConstructor(errors); } } @Override protected void validateFields(List<Throwable> errors) { super.validateFields(errors); if (getInjectionType() == InjectionType.FIELD) { List<FrameworkField> annotatedFieldsByParameter = getAnnotatedFieldsByParameter(); int[] usedIndices = new int[annotatedFieldsByParameter.size()]; for (FrameworkField each : annotatedFieldsByParameter) { int index = each.getField().getAnnotation(Parameter.class) .value(); if (index < 0 || index > annotatedFieldsByParameter.size() - 1) { errors.add(new Exception("Invalid @Parameter value: " + index + ". @Parameter fields counted: " + annotatedFieldsByParameter.size() + ". Please use an index between 0 and " + (annotatedFieldsByParameter.size() - 1) + ".")); } else { usedIndices[index]++; } } for (int index = 0; index < usedIndices.length; index++) { int numberOfUse = usedIndices[index]; if (numberOfUse == 0) { errors.add(new Exception("@Parameter(" + index + ") is never used.")); } else if (numberOfUse > 1) { errors.add(new Exception("@Parameter(" + index + ") is used more than once (" + numberOfUse + ").")); } } } } @Override protected Statement classBlock(RunNotifier notifier) { Statement statement = childrenInvoker(notifier); statement = withBeforeParams(statement); statement = withAfterParams(statement); return statement; } private Statement withBeforeParams(Statement statement) { List<FrameworkMethod> befores = getTestClass() .getAnnotatedMethods(Parameterized.BeforeParam.class); return befores.isEmpty() ? statement : new RunBeforeParams(statement, befores); } private class RunBeforeParams extends RunBefores { RunBeforeParams(Statement next, List<FrameworkMethod> befores) { super(next, befores, null); } @Override protected void invokeMethod(FrameworkMethod method) throws Throwable { int paramCount = method.getMethod().getParameterTypes().length; method.invokeExplosively(null, paramCount == 0 ? (Object[]) null : parameters); } } private Statement withAfterParams(Statement statement) { List<FrameworkMethod> afters = getTestClass() .getAnnotatedMethods(Parameterized.AfterParam.class); return afters.isEmpty() ? statement : new RunAfterParams(statement, afters); } private class RunAfterParams extends RunAfters { RunAfterParams(Statement next, List<FrameworkMethod> afters) { super(next, afters, null); } @Override protected void invokeMethod(FrameworkMethod method) throws Throwable { int paramCount = method.getMethod().getParameterTypes().length; method.invokeExplosively(null, paramCount == 0 ? (Object[]) null : parameters); } } @Override protected Annotation[] getRunnerAnnotations() { Annotation[] allAnnotations = super.getRunnerAnnotations(); Annotation[] annotationsWithoutRunWith = new Annotation[allAnnotations.length - 1]; int i = 0; for (Annotation annotation: allAnnotations) { if (!annotation.annotationType().equals(RunWith.class)) { annotationsWithoutRunWith[i] = annotation; ++i; } } return annotationsWithoutRunWith; } private List<FrameworkField> getAnnotatedFieldsByParameter() { return getTestClass().getAnnotatedFields(Parameter.class); } private InjectionType getInjectionType() { if (fieldsAreAnnotated()) { return InjectionType.FIELD; } else { return InjectionType.CONSTRUCTOR; } } private boolean fieldsAreAnnotated() { return !getAnnotatedFieldsByParameter().isEmpty(); } }