Skip to content

Commit f5f1ace

Browse files
authored
[Spring] Start and stop test context once per scenario (#2517)
The `TestContextAdaptor` would start the scenario scope once at the start of each test, then another one to run the after test method hooks. Refactored code to make the order of operations more obvious. Also prevented scenario scopes from being registered more then once. Though because `CucumberScenarioScope` delegates everything to `CucumberTestContext` this effectively only reduces the log spam a bit at debug level. Fixes: #2516
1 parent 3df1505 commit f5f1ace

File tree

6 files changed

+53
-26
lines changed

6 files changed

+53
-26
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
2828
- `org.glassfish.jaxb:jaxb-runtime`
2929
- [TestNG] Remove spurious Optional\[<Feature Name>] from test name ([#2488](https://github.com/cucumber/cucumber-jvm/pull/2488) M.P. Korstanje)
3030
* [BOM] Add missing dependencies to bill of materials ([#2496](https://github.com/cucumber/cucumber-jvm/pull/2496) M.P. Korstanje)
31+
* [Spring] Start and stop test context once per scenario ([#2517](https://github.com/cucumber/cucumber-jvm/pull/2517) M.P. Korstanje)
3132

3233
## [7.2.3] (2022-01-13)
3334

spring/src/main/java/io/cucumber/spring/CucumberScenarioScope.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@ public Object resolveContextualObject(String key) {
3737
@Override
3838
public String getConversationId() {
3939
CucumberTestContext context = CucumberTestContext.getInstance();
40-
return context.getId();
40+
return context.getId()
41+
.map(id -> "cucumber_test_context_" + id)
42+
.orElse(null);
4143
}
4244

4345
}

spring/src/main/java/io/cucumber/spring/CucumberTestContext.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import java.util.HashMap;
66
import java.util.Map;
7+
import java.util.Optional;
78
import java.util.concurrent.atomic.AtomicInteger;
89

910
@API(status = API.Status.STABLE)
@@ -31,8 +32,8 @@ void start() {
3132
sessionId = sessionCounter.incrementAndGet();
3233
}
3334

34-
String getId() {
35-
return "cucumber_test_context_" + sessionId;
35+
Optional<Integer> getId() {
36+
return Optional.ofNullable(sessionId);
3637
}
3738

3839
void stop() {

spring/src/main/java/io/cucumber/spring/SpringFactory.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@
2121
import java.util.HashSet;
2222
import java.util.Set;
2323

24-
import static io.cucumber.spring.TestContextAdaptor.createTestContextManagerAdaptor;
25-
2624
/**
2725
* Spring based implementation of ObjectFactory.
2826
* <p>
@@ -151,7 +149,7 @@ public void start() {
151149
// a singleton and reused between scenarios and shared between
152150
// threads.
153151
TestContextManager testContextManager = new TestContextManager(withCucumberContextConfiguration);
154-
testContextAdaptor = createTestContextManagerAdaptor(testContextManager, stepClasses);
152+
testContextAdaptor = new TestContextAdaptor(testContextManager, stepClasses);
155153
testContextAdaptor.start();
156154
}
157155

spring/src/main/java/io/cucumber/spring/TestContextAdaptor.java

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import io.cucumber.core.backend.CucumberBackendException;
44
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
5+
import org.springframework.beans.factory.config.Scope;
56
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
67
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
78
import org.springframework.context.ConfigurableApplicationContext;
@@ -13,34 +14,24 @@
1314

1415
import static io.cucumber.spring.CucumberTestContext.SCOPE_CUCUMBER_GLUE;
1516

16-
abstract class TestContextAdaptor {
17+
class TestContextAdaptor {
1718

1819
private static final Object monitor = new Object();
1920

2021
private final TestContextManager delegate;
2122
private final ConfigurableApplicationContext applicationContext;
2223
private final Collection<Class<?>> glueClasses;
2324

24-
protected TestContextAdaptor(
25-
TestContextManager delegate,
26-
ConfigurableApplicationContext applicationContext,
27-
Collection<Class<?>> glueClasses
28-
) {
29-
this.delegate = delegate;
30-
this.applicationContext = applicationContext;
31-
this.glueClasses = glueClasses;
32-
}
33-
34-
static TestContextAdaptor createTestContextManagerAdaptor(
25+
TestContextAdaptor(
3526
TestContextManager delegate,
3627
Collection<Class<?>> glueClasses
3728
) {
3829
TestContext testContext = delegate.getTestContext();
3930
ConfigurableApplicationContext applicationContext = (ConfigurableApplicationContext) testContext
4031
.getApplicationContext();
41-
return new TestContextAdaptor(delegate, applicationContext, glueClasses) {
42-
43-
};
32+
this.delegate = delegate;
33+
this.applicationContext = applicationContext;
34+
this.glueClasses = glueClasses;
4435
}
4536

4637
public final void start() {
@@ -50,15 +41,15 @@ public final void start() {
5041
// #1153, #1148, #1106) we do this serially.
5142
synchronized (monitor) {
5243
registerGlueCodeScope(applicationContext);
53-
notifyContextManagerAboutTestClassStarted();
5444
registerStepClassBeanDefinitions(applicationContext.getBeanFactory());
5545
}
46+
notifyContextManagerAboutBeforeTestClass();
47+
CucumberTestContext.getInstance().start();
5648
notifyTestContextManagerAboutBeforeTestMethod();
5749
}
5850

5951
private void notifyTestContextManagerAboutBeforeTestMethod() {
6052
try {
61-
CucumberTestContext.getInstance().start();
6253
Class<?> testClass = delegate.getTestContext().getTestClass();
6354
Object testContextInstance = applicationContext.getBean(testClass);
6455
Method dummyMethod = TestContextAdaptor.class.getMethod("cucumberDoesNotHaveASingleTestMethod");
@@ -70,12 +61,18 @@ private void notifyTestContextManagerAboutBeforeTestMethod() {
7061

7162
final void registerGlueCodeScope(ConfigurableApplicationContext context) {
7263
while (context != null) {
73-
context.getBeanFactory().registerScope(SCOPE_CUCUMBER_GLUE, new CucumberScenarioScope());
64+
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
65+
// Scenario scope may have already been registered by another
66+
// thread.
67+
Scope registeredScope = beanFactory.getRegisteredScope(SCOPE_CUCUMBER_GLUE);
68+
if (registeredScope == null) {
69+
beanFactory.registerScope(SCOPE_CUCUMBER_GLUE, new CucumberScenarioScope());
70+
}
7471
context = (ConfigurableApplicationContext) context.getParent();
7572
}
7673
}
7774

78-
private void notifyContextManagerAboutTestClassStarted() {
75+
private void notifyContextManagerAboutBeforeTestClass() {
7976
try {
8077
delegate.beforeTestClass();
8178
} catch (Exception e) {
@@ -106,6 +103,10 @@ private void registerStepClassBeanDefinition(BeanDefinitionRegistry registry, Cl
106103
public final void stop() {
107104
notifyTestContextManagerAboutAfterTestMethod();
108105
CucumberTestContext.getInstance().stop();
106+
notifyTestContextManagerAboutAfterTestClass();
107+
}
108+
109+
private void notifyTestContextManagerAboutAfterTestClass() {
109110
try {
110111
delegate.afterTestClass();
111112
} catch (Exception e) {
@@ -115,7 +116,6 @@ public final void stop() {
115116

116117
private void notifyTestContextManagerAboutAfterTestMethod() {
117118
try {
118-
CucumberTestContext.getInstance().start();
119119
Class<?> testClass = delegate.getTestContext().getTestClass();
120120
Object testContextInstance = applicationContext.getBean(testClass);
121121
Method dummyMethod = TestContextAdaptor.class.getMethod("cucumberDoesNotHaveASingleTestMethod");

spring/src/test/java/io/cucumber/spring/SpringFactoryTest.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import org.springframework.beans.factory.annotation.Value;
2424
import org.springframework.test.context.ContextConfiguration;
2525

26+
import java.util.Optional;
27+
2628
import static org.hamcrest.CoreMatchers.containsString;
2729
import static org.hamcrest.CoreMatchers.startsWith;
2830
import static org.hamcrest.MatcherAssert.assertThat;
@@ -32,6 +34,8 @@
3234
import static org.hamcrest.core.IsNull.notNullValue;
3335
import static org.junit.jupiter.api.Assertions.assertAll;
3436
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
37+
import static org.junit.jupiter.api.Assertions.assertEquals;
38+
import static org.junit.jupiter.api.Assertions.assertNull;
3539
import static org.junit.jupiter.api.Assertions.assertThrows;
3640
import static org.junit.jupiter.api.Assertions.assertTrue;
3741

@@ -59,6 +63,27 @@ void shouldGiveUsNewStepInstancesForEachScenario() {
5963
() -> assertThat(o2, is(not(equalTo(o1)))));
6064
}
6165

66+
@Test
67+
void shouldStartOneCucumberContextForEachScenario() {
68+
final ObjectFactory factory = new SpringFactory();
69+
factory.addClass(BellyStepDefinitions.class);
70+
71+
// Scenario 1
72+
assertTrue(CucumberTestContext.getInstance().getId().isEmpty());
73+
factory.start();
74+
Optional<Integer> testContextId1 = CucumberTestContext.getInstance().getId();
75+
factory.stop();
76+
77+
// Scenario 2
78+
assertTrue(CucumberTestContext.getInstance().getId().isEmpty());
79+
factory.start();
80+
Optional<Integer> testContextId2 = CucumberTestContext.getInstance().getId();
81+
factory.stop();
82+
assertTrue(CucumberTestContext.getInstance().getId().isEmpty());
83+
84+
assertEquals(testContextId1.get() + 1, testContextId2.get());
85+
}
86+
6287
@Test
6388
void shouldNeverCreateNewApplicationBeanInstances() {
6489
// Feature 1

0 commit comments

Comments
 (0)