Skip to content

Commit 148c2f8

Browse files
committed
[Spring] Document CucumberContextConfiguration semantics
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 2aae176 commit 148c2f8

File tree

3 files changed

+87
-2
lines changed

3 files changed

+87
-2
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
1010
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
1111

1212
## [Unreleased]
13+
### Fixed
14+
- [Spring] Document `@CucumberContextConfiguration` semantics ([#2887](https://github.com/cucumber/cucumber-jvm/pull/2887) M.P. Korstanje)
1315

1416
## [7.18.0] - 2024-05-17
1517
### Added

cucumber-spring/README.md

+75-1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,13 @@ 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 features that depend on a test execution listener such
51+
as mock beans will work on the annotated class - but not on other step definition
52+
classes. Features that depend on initializing beans - such as AspectJ - will not
53+
work on the annotated class - but will work on other step definition classes.
54+
4855
For more information configuring Spring tests see:
4956
- [Spring Framework Documentation - Testing](https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/testing.html)
5057
- [Spring Boot Features - Testing](https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-testing)
@@ -97,7 +104,8 @@ Repeat as needed.
97104
## Accessing the application context
98105

99106
Components from the application context can be accessed by autowiring.
100-
Annotate a field in your step definition class with `@Autowired`.
107+
108+
Either annotate a field in your step definition class with `@Autowired`
101109

102110
```java
103111
package com.example.app;
@@ -117,6 +125,72 @@ public class MyStepDefinitions {
117125
}
118126
```
119127

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

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

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

+10-1
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 other step
40+
* definition classes.</li>
41+
* <li></li>
3342
* </ul>
3443
*/
3544
@Retention(RetentionPolicy.RUNTIME)

0 commit comments

Comments
 (0)