1010import org .springframework .test .context .TestContextManager ;
1111
1212import java .lang .reflect .Method ;
13+ import java .util .ArrayDeque ;
1314import java .util .Collection ;
15+ import java .util .Deque ;
1416
1517import static io .cucumber .spring .CucumberTestContext .SCOPE_CUCUMBER_GLUE ;
18+ import static org .springframework .beans .factory .config .AutowireCapableBeanFactory .AUTOWIRE_NO ;
1619
1720class TestContextAdaptor {
1821
@@ -21,6 +24,7 @@ class TestContextAdaptor {
2124 private final TestContextManager delegate ;
2225 private final ConfigurableApplicationContext applicationContext ;
2326 private final Collection <Class <?>> glueClasses ;
27+ private final Deque <Runnable > stopInvocations = new ArrayDeque <>();
2428 private Object delegateTestInstance ;
2529
2630 TestContextAdaptor (
@@ -44,23 +48,70 @@ public final void start() {
4448 registerGlueCodeScope (applicationContext );
4549 registerStepClassBeanDefinitions (applicationContext .getBeanFactory ());
4650 }
51+ stopInvocations .push (this ::notifyTestContextManagerAboutAfterTestClass );
4752 notifyContextManagerAboutBeforeTestClass ();
48- CucumberTestContext .getInstance ().start ();
53+ stopInvocations .push (this ::stopCucumberTestContext );
54+ startCucumberTestContext ();
55+ stopInvocations .push (this ::disposeTestInstance );
56+ createAndPrepareTestInstance ();
57+ stopInvocations .push (this ::notifyTestContextManagerAboutAfterTestMethod );
4958 notifyTestContextManagerAboutBeforeTestMethod ();
59+ stopInvocations .push (this ::notifyTestContextManagerAboutAfterTestExecution );
60+ notifyTestContextManagerAboutBeforeExecution ();
5061 }
5162
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.
5394 try {
5495 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 ();
57108 delegate .beforeTestMethod (delegateTestInstance , dummyMethod );
58109 } catch (Exception e ) {
59110 throw new CucumberBackendException (e .getMessage (), e );
60111 }
61112 }
62113
63- final void registerGlueCodeScope (ConfigurableApplicationContext context ) {
114+ private void registerGlueCodeScope (ConfigurableApplicationContext context ) {
64115 while (context != null ) {
65116 ConfigurableListableBeanFactory beanFactory = context .getBeanFactory ();
66117 // Scenario scope may have already been registered by another
@@ -73,15 +124,15 @@ final void registerGlueCodeScope(ConfigurableApplicationContext context) {
73124 }
74125 }
75126
76- private void notifyContextManagerAboutBeforeTestClass () {
127+ private void notifyTestContextManagerAboutBeforeExecution () {
77128 try {
78- delegate .beforeTestClass ( );
129+ delegate .beforeTestExecution ( delegateTestInstance , getDummyMethod () );
79130 } catch (Exception e ) {
80131 throw new CucumberBackendException (e .getMessage (), e );
81132 }
82133 }
83134
84- final void registerStepClassBeanDefinitions (ConfigurableListableBeanFactory beanFactory ) {
135+ private void registerStepClassBeanDefinitions (ConfigurableListableBeanFactory beanFactory ) {
85136 BeanDefinitionRegistry registry = (BeanDefinitionRegistry ) beanFactory ;
86137 for (Class <?> glue : glueClasses ) {
87138 registerStepClassBeanDefinition (registry , glue );
@@ -102,18 +153,23 @@ private void registerStepClassBeanDefinition(BeanDefinitionRegistry registry, Cl
102153 }
103154
104155 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 ;
113168 }
114- CucumberTestContext .getInstance ().stop ();
115169 }
116- notifyTestContextManagerAboutAfterTestClass ();
170+ if (lastException != null ) {
171+ throw lastException ;
172+ }
117173 }
118174
119175 private void notifyTestContextManagerAboutAfterTestClass () {
@@ -124,11 +180,35 @@ private void notifyTestContextManagerAboutAfterTestClass() {
124180 }
125181 }
126182
183+ private void stopCucumberTestContext () {
184+ CucumberTestContext .getInstance ().stop ();
185+ }
186+
187+ private void disposeTestInstance () {
188+ delegateTestInstance = null ;
189+ }
190+
127191 private void notifyTestContextManagerAboutAfterTestMethod () {
128192 try {
129193 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 );
132212 } catch (Exception e ) {
133213 throw new CucumberBackendException (e .getMessage (), e );
134214 }
@@ -138,6 +218,14 @@ final <T> T getInstance(Class<T> type) {
138218 return applicationContext .getBean (type );
139219 }
140220
221+ private Method getDummyMethod () {
222+ try {
223+ return TestContextAdaptor .class .getMethod ("cucumberDoesNotHaveASingleTestMethod" );
224+ } catch (NoSuchMethodException e ) {
225+ throw new RuntimeException (e );
226+ }
227+ }
228+
141229 public void cucumberDoesNotHaveASingleTestMethod () {
142230
143231 }
0 commit comments