View Javadoc
1   package org.junit.internal.builders;
2   
3   import org.junit.runner.RunWith;
4   import org.junit.runner.Runner;
5   import org.junit.runners.model.InitializationError;
6   import org.junit.runners.model.RunnerBuilder;
7   
8   import java.lang.reflect.Modifier;
9   
10  
11  /**
12   * The {@code AnnotatedBuilder} is a strategy for constructing runners for test class that have been annotated with the
13   * {@code @RunWith} annotation. All tests within this class will be executed using the runner that was specified within
14   * the annotation.
15   * <p>
16   * If a runner supports inner member classes, the member classes will inherit the runner from the enclosing class, e.g.:
17   * <pre>
18   * &#064;RunWith(MyRunner.class)
19   * public class MyTest {
20   *     // some tests might go here
21   *
22   *     public class MyMemberClass {
23   *         &#064;Test
24   *         public void thisTestRunsWith_MyRunner() {
25   *             // some test logic
26   *         }
27   *
28   *         // some more tests might go here
29   *     }
30   *
31   *     &#064;RunWith(AnotherRunner.class)
32   *     public class AnotherMemberClass {
33   *         // some tests might go here
34   *
35   *         public class DeepInnerClass {
36   *             &#064;Test
37   *             public void thisTestRunsWith_AnotherRunner() {
38   *                 // some test logic
39   *             }
40   *         }
41   *
42   *         public class DeepInheritedClass extends SuperTest {
43   *             &#064;Test
44   *             public void thisTestRunsWith_SuperRunner() {
45   *                 // some test logic
46   *             }
47   *         }
48   *     }
49   * }
50   *
51   * &#064;RunWith(SuperRunner.class)
52   * public class SuperTest {
53   *     // some tests might go here
54   * }
55   * </pre>
56   * The key points to note here are:
57   * <ul>
58   *     <li>If there is no RunWith annotation, no runner will be created.</li>
59   *     <li>The resolve step is inside-out, e.g. the closest RunWith annotation wins</li>
60   *     <li>RunWith annotations are inherited and work as if the class was annotated itself.</li>
61   *     <li>The default JUnit runner does not support inner member classes,
62   *         so this is only valid for custom runners that support inner member classes.</li>
63   *     <li>Custom runners with support for inner classes may or may not support RunWith annotations for member
64   *         classes. Please refer to the custom runner documentation.</li>
65   * </ul>
66   *
67   * @see org.junit.runners.model.RunnerBuilder
68   * @see org.junit.runner.RunWith
69   * @since 4.0
70   */
71  public class AnnotatedBuilder extends RunnerBuilder {
72      private static final String CONSTRUCTOR_ERROR_FORMAT = "Custom runner class %s should have a public constructor with signature %s(Class testClass)";
73  
74      private final RunnerBuilder suiteBuilder;
75  
76      public AnnotatedBuilder(RunnerBuilder suiteBuilder) {
77          this.suiteBuilder = suiteBuilder;
78      }
79  
80      @Override
81      public Runner runnerForClass(Class<?> testClass) throws Exception {
82          for (Class<?> currentTestClass = testClass; currentTestClass != null;
83               currentTestClass = getEnclosingClassForNonStaticMemberClass(currentTestClass)) {
84              RunWith annotation = currentTestClass.getAnnotation(RunWith.class);
85              if (annotation != null) {
86                  return buildRunner(annotation.value(), testClass);
87              }
88          }
89  
90          return null;
91      }
92  
93      private Class<?> getEnclosingClassForNonStaticMemberClass(Class<?> currentTestClass) {
94          if (currentTestClass.isMemberClass() && !Modifier.isStatic(currentTestClass.getModifiers())) {
95              return currentTestClass.getEnclosingClass();
96          } else {
97              return null;
98          }
99      }
100 
101     public Runner buildRunner(Class<? extends Runner> runnerClass,
102             Class<?> testClass) throws Exception {
103         try {
104             return runnerClass.getConstructor(Class.class).newInstance(testClass);
105         } catch (NoSuchMethodException e) {
106             try {
107                 return runnerClass.getConstructor(Class.class,
108                         RunnerBuilder.class).newInstance(testClass, suiteBuilder);
109             } catch (NoSuchMethodException e2) {
110                 String simpleName = runnerClass.getSimpleName();
111                 throw new InitializationError(String.format(
112                         CONSTRUCTOR_ERROR_FORMAT, simpleName, simpleName));
113             }
114         }
115     }
116 }