Skip to content

Commit a50d1bb

Browse files
authored
[Spring] Document CucumberContextConfiguration semantics (#2887)
Cucumber uses Spring Test Context Manager framework. This framework was written for JUnit and assumes that there is a "test instance". Cucumber however uses multiple step definition classes and so it has multiple test instances. Originally `@CucumberContextConfiguration` was added to signal to Spring which class should be used to configure the application context from. But as people also expected mock beans and other features provided by Springs test execution listeners to work (#2661) the annotated instance was only instantiated but never initialized by Spring. This changed the semantics somewhat as now features that depend on the bean being initialized stopped working (#2886). Unfortunately, there is little that can be done here. Spring expects that the instance provided to the Test Context Manager to be an uninitialized bean. The solution for this is to put the context configuration and step definitions in different classes. Cleaning up the examples to follow this pattern should avoid this problem somewhat in the future. Though I won't go as far as recommending people do this. Putting everything in one class looks quite nice. And generally still works. Closes: #2886
1 parent 36a0626 commit a50d1bb

File tree

3 files changed

+90
-2
lines changed

3 files changed

+90
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
## [Unreleased]
1313
### Fixed
14+
- [Spring] Document `@CucumberContextConfiguration` semantics ([#2887](https://github.com/cucumber/cucumber-jvm/pull/2887) M.P. Korstanje)
1415
- [Core] Enhanced stack trace to include step location for better debugging in case of datatable conversion errors ([#2908](https://github.com/cucumber/cucumber-jvm/pull/2908) Thomas Deblock)
1516
- [Archetype] Set `cucumber.junit-platform.naming-strategy` to `long` when using Surefire.
1617

cucumber-spring/README.md

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,17 @@ public class CucumberSpringConfiguration {
4545
Note: Cucumber Spring uses Spring's `TestContextManager` framework internally.
4646
As a result, a single Cucumber scenario will mostly behave like a JUnit test.
4747

48+
The class annotated with `@CucumberContextConfiguration` is instantiated but not
49+
initialized by Spring. Instead, this instance is processed by Springs test
50+
execution listeners. So Spring features that depend on a test execution
51+
listeners, such as mock beans, will work on the annotated class - but not on
52+
other step definition classes.
53+
54+
Step definition classes are instantiated and initialized by Spring. Features
55+
that depend on beans initialisation, such as AspectJ, will work on step
56+
definition classes - but not on the `@CucumberContextConfiguration` annotated
57+
class.
58+
4859
For more information configuring Spring tests see:
4960
- [Spring Framework Documentation - Testing](https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/testing.html)
5061
- [Spring Boot Features - Testing](https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-testing)
@@ -97,7 +108,8 @@ Repeat as needed.
97108
## Accessing the application context
98109

99110
Components from the application context can be accessed by autowiring.
100-
Annotate a field in your step definition class with `@Autowired`.
111+
112+
Either annotate a field in your step definition class with `@Autowired`
101113

102114
```java
103115
package com.example.app;
@@ -117,6 +129,72 @@ public class MyStepDefinitions {
117129
}
118130
```
119131

132+
Or declare a dependency through the constructor:
133+
134+
```java
135+
package com.example.app;
136+
137+
import io.cucumber.java.en.Given;
138+
139+
public class MyStepDefinitions {
140+
141+
private final MyService myService;
142+
143+
public MyStepDefinitions(MyService myService){
144+
this.myService = myService;
145+
}
146+
147+
@Given("feed back is requested from my service")
148+
public void feed_back_is_requested(){
149+
myService.requestFeedBack();
150+
}
151+
}
152+
```
153+
154+
## Using Mock Beans
155+
156+
To use mock beans, declare a mock bean in the context configuration.
157+
158+
```java
159+
package com.example.app;
160+
161+
import org.springframework.beans.factory.annotation.Autowired;
162+
import org.springframework.boot.test.context.SpringBootTest;
163+
import org.springframework.boot.test.mock.mockito.MockBean;
164+
165+
import io.cucumber.spring.CucumberContextConfiguration;
166+
167+
@CucumberContextConfiguration
168+
@SpringBootTest(classes = TestConfig.class)
169+
@MockBean(MyService.class)
170+
public class CucumberSpringConfiguration {
171+
172+
}
173+
```
174+
175+
Then in your step definitions, use the mock as you would normally.
176+
177+
```java
178+
package com.example.app;
179+
180+
import org.springframework.beans.factory.annotation.Autowired;
181+
import io.cucumber.java.en.Given;
182+
183+
import static org.mockito.Mockito.mockingDetails;
184+
import static org.springframework.test.util.AssertionErrors.assertTrue;
185+
186+
public class MyStepDefinitions {
187+
188+
@Autowired
189+
private MyService myService;
190+
191+
@Given("my service is a mock")
192+
public void feed_back_is_requested(){
193+
assertTrue(mockingDetails(myService).isMock());
194+
}
195+
}
196+
```
197+
120198
## Sharing State
121199

122200
Cucumber Spring creates an application context and uses Spring's

cucumber-spring/src/main/java/io/cucumber/spring/CucumberContextConfiguration.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
* public class CucumberSpringConfiguration {
2222
* }
2323
* </pre>
24-
*
24+
* <p>
2525
* Notes:
2626
* <ul>
2727
* <li>Only one glue class should be annotated with
@@ -30,6 +30,15 @@
3030
* <li>Cucumber Spring uses Spring's {@code TestContextManager} framework
3131
* internally. As a result a single Cucumber scenario will mostly behave like a
3232
* JUnit test.</li>
33+
* <li>The class annotated with {@code CucumberContextConfiguration} is
34+
* instantiated but not initialized by Spring. This instance is processed by
35+
* Springs {@link org.springframework.test.context.TestExecutionListener
36+
* TestExecutionListeners}. So features that depend on a test execution listener
37+
* such as mock beans will work on the annotated class - but not on other step
38+
* definition classes. Features that depend on initializing beans - such as
39+
* AspectJ - will not work on the annotated class - but will work on step
40+
* definition classes.</li>
41+
* <li></li>
3342
* </ul>
3443
*/
3544
@Retention(RetentionPolicy.RUNTIME)

0 commit comments

Comments
 (0)