2
2
3
3
import io .cucumber .core .backend .CucumberBackendException ;
4
4
import io .cucumber .core .backend .ObjectFactory ;
5
- import io .cucumber .core .logging .Logger ;
6
- import io .cucumber .core .logging .LoggerFactory ;
7
5
import org .apiguardian .api .API ;
8
6
import org .springframework .beans .BeansException ;
9
7
import org .springframework .stereotype .Component ;
8
+ import org .springframework .test .annotation .DirtiesContext ;
10
9
import org .springframework .test .context .BootstrapWith ;
11
10
import org .springframework .test .context .ContextConfiguration ;
12
11
import org .springframework .test .context .ContextHierarchy ;
13
12
import org .springframework .test .context .TestContextManager ;
13
+ import org .springframework .test .context .web .WebAppConfiguration ;
14
14
15
15
import java .lang .annotation .Annotation ;
16
16
import java .util .ArrayDeque ;
20
20
import java .util .HashSet ;
21
21
import java .util .Set ;
22
22
23
- import static io .cucumber .spring .TestContextAdaptor .createClassPathXmlApplicationContextAdaptor ;
24
- import static io .cucumber .spring .TestContextAdaptor .createGenericApplicationContextAdaptor ;
25
23
import static io .cucumber .spring .TestContextAdaptor .createTestContextManagerAdaptor ;
26
- import static java .util .Arrays .asList ;
27
24
28
25
/**
29
26
* Spring based implementation of ObjectFactory.
30
27
* <p>
31
28
* Application beans are accessible from the step definitions using autowiring
32
29
* (with annotations).
33
30
* <p>
34
- * SpringFactory uses TestContextManager to manage the spring context. The step definitions are added to the
35
- * TestContextManagers context and the context is reloaded for each scenario.
36
- * <p>
37
- * The spring context can be configured by:
38
- * <ul>
39
- * <li>Annotating one step definition with: @{@link ContextConfiguration}, @{@link ContextHierarchy}
40
- * or @{@link BootstrapWith}. This step definition can also be annotated
41
- * with @{@link org.springframework.test.context.web.WebAppConfiguration}
42
- * or @{@link org.springframework.test.annotation.DirtiesContext} annotation.
43
- * </li>
44
- * <li>Deprecated: If no step definition class with @ContextConfiguration or @ContextHierarchy
45
- * is found, it will try to load cucumber.xml from the classpath.</li>
46
- * </ul>
31
+ * The spring context can be configured by annotating one glue class with a @{@link CucumberContextConfiguration} and
32
+ * any one of the following @{@link ContextConfiguration}, @{@link ContextHierarchy} or @{@link BootstrapWith}.
33
+ * This glue class can also be annotated with @{@link WebAppConfiguration} or @{@link DirtiesContext} annotation.
47
34
* <p>
48
35
* Notes:
49
36
* <ul>
50
- * <li>
51
- * Step definitions should not be annotated with @{@link Component} or other annotations that mark it as eligible for
52
- * detection by classpath scanning. When a step definition class is annotated by @Component or an annotation that has
53
- * the @Component stereotype an exception will be thrown
54
- * </li>
55
- * <li>
56
- * If more that one glue class is used to configure the spring context an exception will be thrown.
57
- * </li>
37
+ * <li>
38
+ * SpringFactory uses Springs TestContextManager framework to manage the spring context. The class annotated
39
+ * with {@code CucumberContextConfiguration} will be use to instantiate the {@link TestContextManager}.
40
+ * </li>
41
+ * <li>
42
+ * If not exactly one glue class is annotated with {@code CucumberContextConfiguration} an exception will be
43
+ * thrown.
44
+ * </li>
45
+ *
46
+ * <li>
47
+ * Step definitions should not be annotated with @{@link Component} or other annotations that mark it as
48
+ * eligible for detection by classpath scanning. When a step definition class is annotated by @Component or an
49
+ * annotation that has the @Component stereotype an exception will be thrown
50
+ * </li>
58
51
* </ul>
59
52
*/
60
53
@ API (status = API .Status .STABLE )
61
54
public final class SpringFactory implements ObjectFactory {
62
55
63
- private static final Logger log = LoggerFactory .getLogger (SpringFactory .class );
64
-
65
- private static final String CUCUMBER_XML = "cucumber.xml" ;
66
56
private final Collection <Class <?>> stepClasses = new HashSet <>();
67
- private Class <?> stepClassWithSpringContext = null ;
57
+ private Class <?> withCucumberContextConfiguration = null ;
68
58
private TestContextAdaptor testContextAdaptor ;
69
59
60
+ @ Override
61
+ public boolean addClass (final Class <?> stepClass ) {
62
+ if (stepClasses .contains (stepClass )) {
63
+ return true ;
64
+ }
65
+
66
+ checkNoComponentAnnotations (stepClass );
67
+ if (hasCucumberContextConfiguration (stepClass )) {
68
+ checkOnlyOneClassHasCucumberContextConfiguration (stepClass );
69
+ withCucumberContextConfiguration = stepClass ;
70
+ }
71
+ stepClasses .add (stepClass );
72
+ return true ;
73
+ }
74
+
70
75
private static void checkNoComponentAnnotations (Class <?> type ) {
71
76
for (Annotation annotation : type .getAnnotations ()) {
72
77
if (hasComponentAnnotation (annotation )) {
@@ -80,143 +85,49 @@ private static void checkNoComponentAnnotations(Class<?> type) {
80
85
}
81
86
}
82
87
83
- private static boolean hasComponentAnnotation (Annotation annotation ) {
84
- return hasAnnotation (annotation , Collections .singleton (Component .class ));
85
- }
86
-
87
- private static boolean hasAnnotation (Annotation annotation , Collection <Class <? extends Annotation >> desired ) {
88
- Set <Class <? extends Annotation >> seen = new HashSet <>();
89
- Deque <Class <? extends Annotation >> toCheck = new ArrayDeque <>();
90
- toCheck .add (annotation .annotationType ());
91
-
92
- while (!toCheck .isEmpty ()) {
93
- Class <? extends Annotation > annotationType = toCheck .pop ();
94
- if (desired .contains (annotationType )) {
95
- return true ;
96
- }
97
-
98
- seen .add (annotationType );
99
- for (Annotation annotationTypesAnnotations : annotationType .getAnnotations ()) {
100
- if (!seen .contains (annotationTypesAnnotations .annotationType ())) {
101
- toCheck .add (annotationTypesAnnotations .annotationType ());
102
- }
103
- }
104
-
105
- }
106
- return false ;
107
- }
108
-
109
- @ Deprecated
110
- private static boolean dependsOnSpringContext (Class <?> type ) {
111
- for (Annotation annotation : type .getAnnotations ()) {
112
- if (annotatedWithSupportedSpringRootTestAnnotations (annotation )) {
113
- return true ;
114
- }
115
- }
116
- return false ;
117
- }
118
-
119
- @ Deprecated
120
- private static boolean annotatedWithSupportedSpringRootTestAnnotations (Annotation type ) {
121
- return hasAnnotation (type , asList (
122
- ContextConfiguration .class ,
123
- ContextHierarchy .class ,
124
- BootstrapWith .class
125
- ));
88
+ private static boolean hasCucumberContextConfiguration (Class <?> stepClass ) {
89
+ return stepClass .getAnnotation (CucumberContextConfiguration .class ) != null ;
126
90
}
127
91
128
- @ Override
129
- public boolean addClass (final Class <?> stepClass ) {
130
- if (!stepClasses .contains (stepClass )) {
131
- checkNoComponentAnnotations (stepClass );
132
- if (dependsOnSpringContext (stepClass ) || hasCucumberContextConfiguration (stepClass )) {
133
- if (stepClassWithSpringContext != null ) {
134
- throw new CucumberBackendException (String .format ("" +
135
- "Glue class %1$s and %2$s both attempt to configure the spring context. Please ensure only one " +
136
- "glue class configures the spring context" , stepClass , stepClassWithSpringContext
137
- ));
138
- }
139
-
140
- if (dependsOnSpringContext (stepClass ) && !hasCucumberContextConfiguration (stepClass )) {
141
- log .warn (() -> String .format (
142
- "Glue class %1$s attempts to configure the spring context but was not annotated with %2$s.\n " +
143
- "Implicit configuration of the spring context is deprecated.\n " +
144
- "Please add the %2$s to %1$s" , stepClass , CucumberContextConfiguration .class .getName ()
145
- ));
146
- }
147
-
148
- stepClassWithSpringContext = stepClass ;
149
- }
150
- stepClasses .add (stepClass );
92
+ private void checkOnlyOneClassHasCucumberContextConfiguration (Class <?> stepClass ) {
93
+ if (withCucumberContextConfiguration != null ) {
94
+ throw new CucumberBackendException (String .format ("" +
95
+ "Glue class %1$s and %2$s are both annotated with @CucumberContextConfiguration.\n " +
96
+ "Please ensure only one class configures the spring context" ,
97
+ stepClass ,
98
+ withCucumberContextConfiguration
99
+ ));
151
100
}
152
- return true ;
153
- }
154
-
155
- private boolean hasCucumberContextConfiguration (Class <?> stepClass ) {
156
- return stepClass .getAnnotation (CucumberContextConfiguration .class ) != null ;
157
101
}
158
102
159
-
160
103
@ Override
161
104
public void start () {
162
- if (stepClassWithSpringContext != null ) {
163
- // The application context created by the TestContextManager is
164
- // a singleton and reused between scenarios and shared between
165
- // threads.
166
- TestContextManager testContextManager = new TestContextManager (stepClassWithSpringContext );
167
- testContextAdaptor = createTestContextManagerAdaptor (testContextManager , stepClasses );
168
- } else if (getClass ().getClassLoader ().getResource (CUCUMBER_XML ) == null ) {
169
- warnAboutDeprecationOfGenericApplicationContext ();
170
- // The generic fallback application context is not shared between
171
- // threads (because the spring factory is not shared) and not reused
172
- // between scenarios because we recreate it each time the spring
173
- // factory starts.
174
- testContextAdaptor = createGenericApplicationContextAdaptor (stepClasses );
175
- } else if (testContextAdaptor == null ) {
176
- warnAboutDeprecationOfCucumberXml ();
177
-
178
- // The xml fallback application context is not shared between
179
- // threads (because the spring factory is not shared) but is reused
180
- // between scenarios.
181
- String [] configLocations = {CUCUMBER_XML };
182
- testContextAdaptor = createClassPathXmlApplicationContextAdaptor (configLocations , stepClasses );
105
+ if (withCucumberContextConfiguration == null ) {
106
+ throw new CucumberBackendException ("" +
107
+ "Please annotate a glue class with some context configuration.\n " +
108
+ "\n " +
109
+ "For example:\n " +
110
+ "\n " +
111
+ " @CucumberContextConfiguration\n " +
112
+ " @SpringBootTest(classes = TestConfig.class)\n " +
113
+ " public class CucumberSpringConfiguration { }" +
114
+ "\n " +
115
+ "Or: \n " +
116
+ "\n " +
117
+ " @CucumberContextConfiguration\n " +
118
+ " @ContextConfiguration( ... )\n " +
119
+ " public class CucumberSpringConfiguration { }"
120
+ );
183
121
}
184
- testContextAdaptor .start ();
185
- }
186
-
187
- private void warnAboutDeprecationOfGenericApplicationContext () {
188
- log .warn (() -> "" +
189
- "Glue glue classes have been annotated with a Spring Context Configuration.\n " +
190
- "Falling back to a generic application context.\n " +
191
- "This fallback has beep deprecated. Please annotate a glue class with some context configuration.\n " +
192
- "\n " +
193
- "For example:\n " +
194
- "\n " +
195
- " @@CucumberContextConfiguration\n " +
196
- " @SpringBootTest(classes = TestConfig.class)\n " +
197
- " public class CucumberSpringConfiguration { }" +
198
- "\n " +
199
- "Or: \n " +
200
- "\n " +
201
- " @@CucumberContextConfiguration\n " +
202
- " @ContextConfiguration( ... )\n " +
203
- " public class CucumberSpringConfiguration { }"
204
- );
205
- }
206
122
207
- private void warnAboutDeprecationOfCucumberXml () {
208
- log .warn (() -> "" +
209
- "You are using cucumber.xml to configure the Spring Application Context.\n " +
210
- "cucumber.xml has been deprecated. Instead consider annotation based configuration.\n " +
211
- "You may create a glue class containing:\n " +
212
- "\n " +
213
- " @@CucumberContextConfiguration\n " +
214
- " @ContextConfiguration(\" classpath:cucumber.xml\" )\n " +
215
- " public class CucumberSpringConfiguration { }"
216
- );
123
+ // The application context created by the TestContextManager is
124
+ // a singleton and reused between scenarios and shared between
125
+ // threads.
126
+ TestContextManager testContextManager = new TestContextManager (withCucumberContextConfiguration );
127
+ testContextAdaptor = createTestContextManagerAdaptor (testContextManager , stepClasses );
128
+ testContextAdaptor .start ();
217
129
}
218
130
219
-
220
131
@ Override
221
132
public void stop () {
222
133
if (testContextAdaptor != null ) {
@@ -233,4 +144,29 @@ public <T> T getInstance(final Class<T> type) {
233
144
}
234
145
}
235
146
236
- }
147
+ private static boolean hasComponentAnnotation (Annotation annotation ) {
148
+ return hasAnnotation (annotation , Collections .singleton (Component .class ));
149
+ }
150
+
151
+ private static boolean hasAnnotation (Annotation annotation , Collection <Class <? extends Annotation >> desired ) {
152
+ Set <Class <? extends Annotation >> seen = new HashSet <>();
153
+ Deque <Class <? extends Annotation >> toCheck = new ArrayDeque <>();
154
+ toCheck .add (annotation .annotationType ());
155
+
156
+ while (!toCheck .isEmpty ()) {
157
+ Class <? extends Annotation > annotationType = toCheck .pop ();
158
+ if (desired .contains (annotationType )) {
159
+ return true ;
160
+ }
161
+
162
+ seen .add (annotationType );
163
+ for (Annotation annotationTypesAnnotations : annotationType .getAnnotations ()) {
164
+ if (!seen .contains (annotationTypesAnnotations .annotationType ())) {
165
+ toCheck .add (annotationTypesAnnotations .annotationType ());
166
+ }
167
+ }
168
+
169
+ }
170
+ return false ;
171
+ }
172
+ }
0 commit comments