Skip to content

Commit 98a38a0

Browse files
authored
[Spring] Deprecate cucumber.xml and implied context configuration (#1940)
The prefered way to use `cucumber-spring` is to annotate a class with both `@CucumberContextConfiguration` and a Spring context configuration annotation such as `@ContextConfiguration`, `@SpringBootTest`, ect. Cucumber currently supports the discovery of context configuration on any step defintion class. This requires a significant amount of code and intimate knowledge of the Spring Framework. By restricting this too only classes annotated with `@CucumberContextConfiguration` we can remove this complexity and simply pass the annotated class to Spring `TestContextManager` framework. If no context configuration is available Cucumber currently also support reading the application context from a magical `cucumber.xml` file or should none exist an empty application context. Both these fallback strategies tend to hide user errors. And removing them will provide more clarity and also remove a nuggest of complexity. Closes: #1904
1 parent 8b60b83 commit 98a38a0

18 files changed

+205
-107
lines changed

spring/README.md

+105-102
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
Cucumber Spring
22
===============
33

4-
Use Cucumber Spring to manage state between steps and for scenarios.
4+
Use Cucumber Spring to share state between steps in a scenario and access the
5+
spring application context.
56

67
Add the `cucumber-spring` dependency to your `pom.xml`:
78

@@ -18,136 +19,141 @@ Add the `cucumber-spring` dependency to your `pom.xml`:
1819
</dependencies>
1920
```
2021

21-
## Annotation Based Configuration
22+
## Configuring the Test Application Context
2223

23-
For your own classes:
24+
To make Cucumber aware of your test configuration you can annotate a
25+
configuration class with `@CucumberContextConfiguration` and with one of the
26+
following annotations: `@ContextConfiguration`, `@ContextHierarchy` or
27+
`@BootstrapWith`. If you are using SpringBoot, you can annotate configuration
28+
class with `@SpringBootTest`.
2429

25-
* Add a `@Component` annotation to each of the classes `cucumber-spring` should
26-
manage.
30+
For example:
2731
```java
28-
package com.example.app;
32+
import com.example.app;
2933

30-
import org.springframework.stereotype.Component;
34+
import org.springframework.boot.test.context.SpringBootTest;
3135

32-
@Component
33-
public class Belly {
34-
private int cukes = 0;
36+
import io.cucumber.spring.CucumberContextConfiguration;
3537

36-
public void setCukes(int cukes) {
37-
this.cukes = cukes;
38-
}
38+
@CucumberContextConfiguration
39+
@SpringBootTest(classes = TestConfig.class)
40+
public class CucumberSpringConfiguration {
3941

40-
public int getCukes() {
41-
return cukes;
42-
}
4342
}
4443
```
45-
* Add the location of your classes to the `@ComponentScan` of your (test)
46-
configuration:
44+
45+
Note: Cucumber Spring uses Springs `TestContextManager` framework internally.
46+
As a result a single Cucumber scenario will mostly behave like a JUnit test.
47+
48+
For more information configuring Spring tests see:
49+
- [Spring Framework Documentation - Testing](https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/testing.html)
50+
- [Spring Boot Features - Testing](https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-testing)
51+
52+
## Accessing the application context
53+
54+
Components from the application context can be accessed by autowiring.
55+
Annotate a field in your step definition class with `@Autowired`.
4756

4857
```java
4958
package com.example.app;
5059

51-
import org.springframework.context.annotation.ComponentScan;
52-
import org.springframework.context.annotation.Configuration;
60+
public class MyStepDefinitions {
61+
62+
@Autowired
63+
private MyService myService;
5364

54-
@Configuration
55-
@ComponentScan("com.example.app")
56-
public class Config {
57-
// the rest of your configuration
65+
@Given("feed back is requested from my service")
66+
public void feed_back_is_requested(){
67+
myService.requestFeedBack();
68+
}
5869
}
5970
```
6071

61-
For classes from other frameworks:
72+
## Sharing State
6273

63-
* You will have to explicitly register them as Beans in your (test) configuration:
74+
Cucumber Spring creates an application context and using Springs
75+
`TestContextManager` framework internally. All scenarios as well as all other
76+
tests (e.g. JUnit) that use the same context configuration will share one
77+
instance of the Spring application. This avoids an expensive startup time.
78+
79+
### Sharing state between steps
80+
81+
To prevent sharing test state between scenarios, beans containing glue code
82+
(i.e. step definitions, hooks, ect) are bound to the `cucumber-glue` scope.
83+
84+
The `cucumber-glue` scope starts prior to a scenario and ends after a scenario.
85+
All beans in this scope will be created before a scenario execution and
86+
disposed at the end of it.
87+
88+
By using the `CucumberTestContext.SCOPE_CUCUMBER_GLUE` additional components
89+
can be added to the glue scope. These components can be used to safely share
90+
state between scenarios.
6491

6592
```java
6693
package com.example.app;
6794

68-
import com.example.other.framework.SomeOtherService;
69-
import org.springframework.context.annotation.Bean;
70-
import org.springframework.context.annotation.ComponentScan;
71-
import org.springframework.context.annotation.Configuration;
95+
import org.springframework.stereotype.Component;
7296
import org.springframework.context.annotation.Scope;
97+
import static io.cucumber.spring.CucumberTestContext;
7398

74-
@Configuration
75-
@ComponentScan("com.example.app")
76-
public class TestConfig {
77-
@Bean
78-
public SomeOtherService someOtherService() {
79-
// return an instance of some other service
80-
}
81-
}
82-
```
83-
84-
To make Cucumber aware of your test configuration you can annotate a
85-
configuration class with `@CucumberContextConfiguration` and with one of the
86-
following annotations: `@ContextConfiguration`, `@ContextHierarchy` or
87-
`@BootstrapWith`. If you are using SpringBoot, you can annotate configuration
88-
class with `@SpringBootTest(classes = TestConfig.class)`.
99+
@Component
100+
@Scope(CucumberTestContext.SCOPE_CUCUMBER_GLUE)
101+
public class TestUserInformation {
89102

90-
For example:
91-
```java
92-
import com.example.app;
93-
import org.springframework.boot.test.context.SpringBootTest;
94-
import io.cucumber.spring.CucumberContextConfiguration;
103+
private User testUser;
95104

96-
@SpringBootTest(classes = TestConfig.class)
97-
@CucumberContextConfiguration
98-
public class SomeServiceStepDefinitions {
105+
public void setTestUser(User testUser) {
106+
this.testUser = testUser;
107+
}
99108

109+
public User getTestUser() {
110+
return testUser;
111+
}
100112

101113
}
102114
```
103115

104-
Now you can use the registered beans by autowiring them where you need them.
116+
The glue scoped component can then be autowired into a step definition:
105117

106-
For example:
107118
```java
108-
import com.example.app;
109-
import org.springframework.beans.factory.annotation.Autowired;
110-
111-
public class SomeServiceStepDefinitions {
112-
113-
@Autowired
114-
SomeService someService;
115-
116-
@Autowired
117-
SomeOtherService someOtherService;
119+
package com.example.app;
118120

119-
// the rest of your step definitions
120-
}
121-
```
121+
public class UserStepDefinitions {
122122

123-
### The Application Context & Cucumber Glue Scope
123+
@Autowired
124+
private UserService userService;
124125

125-
Cucumber Spring creates an application context. This application context is
126-
shared between scenarios.
126+
@Autowired
127+
private TestUserInformation testUserInformation;
127128

128-
To prevent sharing state between scenarios, beans containing glue code
129-
(i.e. step definitions, hooks, ect) are bound to the `cucumber-glue` scope.
129+
@Given("there is a user")
130+
public void there_is_as_user() {
131+
User testUser = userService.createUser();
132+
testUserInformation.setTestUser(testUser);
133+
}
134+
}
130135

131-
The `cucumber-glue` scope starts prior to a scenario and end after a scenario.
132-
Beans in this scope are created prior to a scenario execution and disposed at
133-
the end of it.
136+
public class PurchaseStepDefinitions {
134137

135-
Changing a Spring bean's scope to `SCOPE_CUCUMBER_GLUE` will bind its lifecycle
136-
to the `cucumber-glue` scope.
138+
@Autowired
139+
private PurchaseService purchaseService;
137140

138-
```java
139-
import org.springframework.stereotype.Component;
140-
import org.springframework.context.annotation.Scope;
141-
import static io.cucumber.spring.CucumberTestContext.SCOPE_CUCUMBER_GLUE;
141+
@Autowired
142+
private TestUserInformation testUserInformation;
142143

143-
@Component
144-
@Scope(SCOPE_CUCUMBER_GLUE)
145-
public class MyComponent {
144+
@When("the user makes a purchase")
145+
public void the_user_makes_a_purchase(){
146+
Order order = ....
147+
User user = testUserInformation.getTestUser();
148+
purchaseService.purchase(user, order);
149+
}
146150
}
147151
```
148152

153+
### Dirtying the application context
154+
149155
If your tests do dirty the application context you can add `@DirtiesContext` to
150-
your test configuration.
156+
your test configuration.
151157

152158
```java
153159
package com.example.app;
@@ -156,28 +162,25 @@ import org.springframework.beans.factory.annotation.Autowired;
156162
import org.springframework.test.annotation.DirtiesContext;
157163
import org.springframework.boot.test.context.SpringBootTest;
158164

165+
import io.cucumber.spring.CucumberContextConfiguration;
166+
167+
@CucumberContextConfiguration
159168
@SpringBootTest(classes = TestConfig.class)
160169
@DirtiesContext
161-
public class SomeServiceStepDefinitions {
162-
163-
@Autowired
164-
private Belly belly; // Each scenario have a new instance of Belly
165-
166-
[...]
167-
170+
public class CucumberSpringConfiguration {
171+
168172
}
169173
```
174+
```java
175+
package com.example.app;
170176

171-
### XML Configuration
177+
public class MyStepDefinitions {
172178

173-
If you are using xml based configuration, you can to register the beans in a
174-
`cucumber.xml` file:
179+
@Autowired
180+
private MyService myService; // Each scenario have a new instance of MyService
175181

176-
```xml
177-
<bean class="com.example.app.MyService"/>
178-
<bean class="com.example.lib.SomeOtherService"/>
182+
}
179183
```
180184

181-
Annotate a configuration class with
182-
`@ContextConfiguration("classpath:cucumber.xml")`
183-
185+
Note: Using `@DirtiesContext` in combination with parallel execution will lead
186+
to undefined behaviour.

0 commit comments

Comments
 (0)