package de.bechte.junit.runners.context; import de.bechte.junit.runners.context.description.ContextDescriber; import de.bechte.junit.runners.context.description.Describer; import de.bechte.junit.runners.context.description.MethodDescriber; import de.bechte.junit.runners.context.processing.ChildExecutor; import de.bechte.junit.runners.context.processing.ContextExecutor; import de.bechte.junit.runners.context.processing.MethodExecutor; import de.bechte.junit.runners.context.processing.ChildResolver; import de.bechte.junit.runners.context.processing.ContextResolver; import de.bechte.junit.runners.context.processing.MethodResolver; import de.bechte.junit.runners.context.statements.RunAll; import de.bechte.junit.runners.context.statements.RunChildren; import de.bechte.junit.runners.context.statements.StatementExecutor; import de.bechte.junit.runners.context.statements.StatementExecutorFactory; import de.bechte.junit.runners.context.statements.builder.ClassStatementBuilder; import de.bechte.junit.runners.context.statements.builder.StatementBuilderFactory; import de.bechte.junit.runners.model.TestClassPool; import de.bechte.junit.runners.validation.BooleanValidator; import de.bechte.junit.runners.validation.ChildrenCountValidator; import de.bechte.junit.runners.validation.ConstructorValidator; import de.bechte.junit.runners.validation.FixtureValidator; import de.bechte.junit.runners.validation.RuleValidator; import de.bechte.junit.runners.validation.TestClassValidator; import org.junit.runner.Description; import org.junit.runner.Runner; import org.junit.runner.notification.RunNotifier; import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.InitializationError; import org.junit.runners.model.Statement; import org.junit.runners.model.TestClass; import java.util.ArrayList; import java.util.List; /** * The {@link HierarchicalContextRunner} allows test classes to have member classes. These member classes are * interpreted as context hierarchies, allowing you to group your JUnit tests that use the same preconditions. * * Please refer to the wiki for a sample and more information: * https://github.com/bechte/junit-hierarchicalcontextrunner/wiki */ public class HierarchicalContextRunner extends Runner { protected final TestClass testClass; protected ChildResolver<FrameworkMethod> methodResolver; protected Describer<FrameworkMethod> methodDescriber; protected ChildExecutor<FrameworkMethod> methodRunner; protected ChildResolver<Class<?>> contextResolver; protected Describer<Class<?>> contextDescriber; protected ChildExecutor<Class<?>> contextRunner; protected StatementExecutor statementExecutor; protected List<ClassStatementBuilder> statementBuilders; public HierarchicalContextRunner(final Class<?> testClass) throws InitializationError { this.testClass = TestClassPool.forClass(testClass); validate(); initialize(); } private void validate() throws InitializationError { final List<Throwable> errors = new ArrayList<Throwable>(); getValidator().validate(testClass, errors); if (!errors.isEmpty()) throw new InitializationError(errors); } /** * Returns a {@link TestClassValidator} that validates the {@link TestClass} instance after the * {@link HierarchicalContextRunner} has been created for the corresponding {@link Class}. To use multiple * {@link TestClassValidator}s, please use the {@link BooleanValidator} AND and OR to group your validators. * * Note: Clients may override this method to add or remove validators. * * @return a {@link TestClassValidator} */ protected TestClassValidator getValidator() { return BooleanValidator.AND( ConstructorValidator.VALID_CONSTRUCTOR, BooleanValidator.OR( ChildrenCountValidator.CONTEXT_HIERARCHIES, ChildrenCountValidator.TEST_METHODS ), FixtureValidator.BEFORE_CLASS_METHODS, FixtureValidator.AFTER_CLASS_METHODS, FixtureValidator.BEFORE_METHODS, FixtureValidator.AFTER_METHODS, FixtureValidator.TEST_METHODS, RuleValidator.CLASS_RULE_VALIDATOR, RuleValidator.CLASS_RULE_METHOD_VALIDATOR, RuleValidator.RULE_VALIDATOR, RuleValidator.RULE_METHOD_VALIDATOR ); } /** * Initializes all dependencies for the {@link HierarchicalContextRunner}. * * Note: Clients may override this method to provide other dependencies. */ protected void initialize() { final StatementExecutorFactory statementExecutorFactory = StatementExecutorFactory.getDefault(); final StatementBuilderFactory statementBuilderFactory = StatementBuilderFactory.getDefault(); methodResolver = new MethodResolver(); methodDescriber = new MethodDescriber(); methodRunner = new MethodExecutor(methodDescriber, statementExecutorFactory.getExecutorForMethods(), statementBuilderFactory.getBuildersForMethods()); contextResolver = new ContextResolver(); contextDescriber = new ContextDescriber(contextResolver, methodResolver, methodDescriber); contextRunner = new ContextExecutor(contextDescriber); statementExecutor = statementExecutorFactory.getExecutorForClasses(); statementBuilders = statementBuilderFactory.getBuildersForClasses(); } @Override public Description getDescription() { return contextDescriber.describe(testClass.getJavaClass()); } @Override public void run(final RunNotifier notifier) { final Description description = getDescription(); Statement statement = runChildren(description, notifier); for (final ClassStatementBuilder builder : statementBuilders) { statement = builder.createStatement(testClass, statement, description, notifier); } statementExecutor.execute(statement, notifier, description); } /** * This method returns a {@link Statement} that is responsible for running all children of the given test class. In * order to run more than one {@link Statement}, please use the {@link RunAll} statement for grouping. * * Note: Clients may override this method. The statement returned should only be responsible for running the * children. Extra work may be registered with the {@code statementBuilders} list which is initialized during the * call of {@link #initialize()}. Please register additional payload, e.g. the run of {@code @BeforeClass}, * {@code @AfterClass} or {@code @ClassRule}, there. {@link de.bechte.junit.runners.context.statements.builder.ClassStatementBuilder}s will be called in the order they are * registered. * * @param description the {@link Description} of the class * @param notifier the {@link RunNotifier} used for this iteration * @return a {@link Statement} that runs all children */ protected Statement runChildren(final Description description, final RunNotifier notifier) { return new RunAll( new RunChildren<FrameworkMethod>(testClass, methodRunner, methodResolver, notifier), new RunChildren<Class<?>>(testClass, contextRunner, contextResolver, notifier) ); } }