10
10
import org .springframework .test .context .TestContextManager ;
11
11
12
12
import java .lang .reflect .Method ;
13
+ import java .util .ArrayDeque ;
13
14
import java .util .Collection ;
15
+ import java .util .Deque ;
14
16
15
17
import static io .cucumber .spring .CucumberTestContext .SCOPE_CUCUMBER_GLUE ;
18
+ import static org .springframework .beans .factory .config .AutowireCapableBeanFactory .AUTOWIRE_NO ;
16
19
17
20
class TestContextAdaptor {
18
21
@@ -21,6 +24,7 @@ class TestContextAdaptor {
21
24
private final TestContextManager delegate ;
22
25
private final ConfigurableApplicationContext applicationContext ;
23
26
private final Collection <Class <?>> glueClasses ;
27
+ private final Deque <Runnable > stopInvocations = new ArrayDeque <>();
24
28
private Object delegateTestInstance ;
25
29
26
30
TestContextAdaptor (
@@ -44,23 +48,70 @@ public final void start() {
44
48
registerGlueCodeScope (applicationContext );
45
49
registerStepClassBeanDefinitions (applicationContext .getBeanFactory ());
46
50
}
51
+ stopInvocations .push (this ::notifyTestContextManagerAboutAfterTestClass );
47
52
notifyContextManagerAboutBeforeTestClass ();
48
- CucumberTestContext .getInstance ().start ();
53
+ stopInvocations .push (this ::stopCucumberTestContext );
54
+ startCucumberTestContext ();
55
+ stopInvocations .push (this ::disposeTestInstance );
56
+ createAndPrepareTestInstance ();
57
+ stopInvocations .push (this ::notifyTestContextManagerAboutAfterTestMethod );
49
58
notifyTestContextManagerAboutBeforeTestMethod ();
59
+ stopInvocations .push (this ::notifyTestContextManagerAboutAfterTestExecution );
60
+ notifyTestContextManagerAboutBeforeExecution ();
50
61
}
51
62
52
- private void notifyTestContextManagerAboutBeforeTestMethod () {
63
+ private void notifyContextManagerAboutBeforeTestClass () {
64
+ try {
65
+ delegate .beforeTestClass ();
66
+ } catch (Exception e ) {
67
+ throw new CucumberBackendException (e .getMessage (), e );
68
+ }
69
+ }
70
+
71
+ private void startCucumberTestContext () {
72
+ CucumberTestContext .getInstance ().start ();
73
+ }
74
+
75
+ private void createAndPrepareTestInstance () {
76
+ // Unlike JUnit, Cucumber does not have a single test class.
77
+ // Springs TestContext however assumes we do, and we are expected to
78
+ // create an instance of it using the default constructor.
79
+ //
80
+ // Users of Cucumber would however like to inject their step
81
+ // definition classes into other step definition classes. This requires
82
+ // that the test instance exists in the application context as a bean.
83
+ //
84
+ // Normally when a bean is pulled from the application context with
85
+ // getBean it is also autowired. This will however conflict with
86
+ // Springs DependencyInjectionTestExecutionListener. So we create
87
+ // a raw bean here.
88
+ //
89
+ // This probably free from side effects, but at some point in the
90
+ // future we may have to accept that the only way forward is to
91
+ // construct instances annotated with @CucumberContextConfiguration
92
+ // using their default constructor and now allow them to be injected
93
+ // into other step definition classes.
53
94
try {
54
95
Class <?> delegateTestClass = delegate .getTestContext ().getTestClass ();
55
- delegateTestInstance = applicationContext .getBean (delegateTestClass );
56
- Method dummyMethod = TestContextAdaptor .class .getMethod ("cucumberDoesNotHaveASingleTestMethod" );
96
+ Object delegateTestInstance = applicationContext .getBeanFactory ().autowire (delegateTestClass , AUTOWIRE_NO ,
97
+ false );
98
+ delegate .prepareTestInstance (delegateTestInstance );
99
+ this .delegateTestInstance = delegateTestInstance ;
100
+ } catch (Exception e ) {
101
+ throw new CucumberBackendException (e .getMessage (), e );
102
+ }
103
+ }
104
+
105
+ private void notifyTestContextManagerAboutBeforeTestMethod () {
106
+ try {
107
+ Method dummyMethod = getDummyMethod ();
57
108
delegate .beforeTestMethod (delegateTestInstance , dummyMethod );
58
109
} catch (Exception e ) {
59
110
throw new CucumberBackendException (e .getMessage (), e );
60
111
}
61
112
}
62
113
63
- final void registerGlueCodeScope (ConfigurableApplicationContext context ) {
114
+ private void registerGlueCodeScope (ConfigurableApplicationContext context ) {
64
115
while (context != null ) {
65
116
ConfigurableListableBeanFactory beanFactory = context .getBeanFactory ();
66
117
// Scenario scope may have already been registered by another
@@ -73,15 +124,15 @@ final void registerGlueCodeScope(ConfigurableApplicationContext context) {
73
124
}
74
125
}
75
126
76
- private void notifyContextManagerAboutBeforeTestClass () {
127
+ private void notifyTestContextManagerAboutBeforeExecution () {
77
128
try {
78
- delegate .beforeTestClass ( );
129
+ delegate .beforeTestExecution ( delegateTestInstance , getDummyMethod () );
79
130
} catch (Exception e ) {
80
131
throw new CucumberBackendException (e .getMessage (), e );
81
132
}
82
133
}
83
134
84
- final void registerStepClassBeanDefinitions (ConfigurableListableBeanFactory beanFactory ) {
135
+ private void registerStepClassBeanDefinitions (ConfigurableListableBeanFactory beanFactory ) {
85
136
BeanDefinitionRegistry registry = (BeanDefinitionRegistry ) beanFactory ;
86
137
for (Class <?> glue : glueClasses ) {
87
138
registerStepClassBeanDefinition (registry , glue );
@@ -102,18 +153,23 @@ private void registerStepClassBeanDefinition(BeanDefinitionRegistry registry, Cl
102
153
}
103
154
104
155
public final void stop () {
105
- // Don't invoke after test method when before test class was not invoked
106
- // this is implicit in the existence of an active the test context
107
- // session. This is not ideal, but Cucumber only supports 1 set of
108
- // before/after semantics while JUnit and Spring have 2 sets.
109
- if (CucumberTestContext .getInstance ().isActive ()) {
110
- if (delegateTestInstance != null ) {
111
- notifyTestContextManagerAboutAfterTestMethod ();
112
- delegateTestInstance = null ;
156
+ // Cucumber only supports 1 set of before/after semantics while JUnit
157
+ // and Spring have 2 sets. So here we use a stack to ensure we don't
158
+ // invoke only the matching after methods for each before methods.
159
+ CucumberBackendException lastException = null ;
160
+ for (Runnable stopInvocation : stopInvocations ) {
161
+ try {
162
+ stopInvocation .run ();
163
+ } catch (CucumberBackendException e ) {
164
+ if (lastException != null ) {
165
+ e .addSuppressed (lastException );
166
+ }
167
+ lastException = e ;
113
168
}
114
- CucumberTestContext .getInstance ().stop ();
115
169
}
116
- notifyTestContextManagerAboutAfterTestClass ();
170
+ if (lastException != null ) {
171
+ throw lastException ;
172
+ }
117
173
}
118
174
119
175
private void notifyTestContextManagerAboutAfterTestClass () {
@@ -124,11 +180,35 @@ private void notifyTestContextManagerAboutAfterTestClass() {
124
180
}
125
181
}
126
182
183
+ private void stopCucumberTestContext () {
184
+ CucumberTestContext .getInstance ().stop ();
185
+ }
186
+
187
+ private void disposeTestInstance () {
188
+ delegateTestInstance = null ;
189
+ }
190
+
127
191
private void notifyTestContextManagerAboutAfterTestMethod () {
128
192
try {
129
193
Object delegateTestInstance = delegate .getTestContext ().getTestInstance ();
130
- Method dummyMethod = TestContextAdaptor .class .getMethod ("cucumberDoesNotHaveASingleTestMethod" );
131
- delegate .afterTestMethod (delegateTestInstance , dummyMethod , null );
194
+ // Cucumber tests can throw exceptions, but we can't currently
195
+ // get at them. So we provide null intentionally.
196
+ // Cucumber also doesn't a single test method, so we provide a
197
+ // dummy instead.
198
+ delegate .afterTestMethod (delegateTestInstance , getDummyMethod (), null );
199
+ } catch (Exception e ) {
200
+ throw new CucumberBackendException (e .getMessage (), e );
201
+ }
202
+ }
203
+
204
+ private void notifyTestContextManagerAboutAfterTestExecution () {
205
+ try {
206
+ Object delegateTestInstance = delegate .getTestContext ().getTestInstance ();
207
+ // Cucumber tests can throw exceptions, but we can't currently
208
+ // get at them. So we provide null intentionally.
209
+ // Cucumber also doesn't a single test method, so we provide a
210
+ // dummy instead.
211
+ delegate .afterTestExecution (delegateTestInstance , getDummyMethod (), null );
132
212
} catch (Exception e ) {
133
213
throw new CucumberBackendException (e .getMessage (), e );
134
214
}
@@ -138,6 +218,14 @@ final <T> T getInstance(Class<T> type) {
138
218
return applicationContext .getBean (type );
139
219
}
140
220
221
+ private Method getDummyMethod () {
222
+ try {
223
+ return TestContextAdaptor .class .getMethod ("cucumberDoesNotHaveASingleTestMethod" );
224
+ } catch (NoSuchMethodException e ) {
225
+ throw new RuntimeException (e );
226
+ }
227
+ }
228
+
141
229
public void cucumberDoesNotHaveASingleTestMethod () {
142
230
143
231
}
0 commit comments