Skip to content

Commit 26329bb

Browse files
authored
[Guice] Inject static fields prior to before all hooks (#2803)
1 parent 5bf1679 commit 26329bb

File tree

5 files changed

+63
-39
lines changed

5 files changed

+63
-39
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
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+
- [Guice] Inject static fields prior to before all hooks ([#2803](https://github.com/cucumber/cucumber-jvm/pull/2803) M.P. Korstanje)
1315

1416
## [7.14.0] - 2023-09-09
1517
### Changed

cucumber-guice/src/main/java/io/cucumber/guice/GuiceFactory.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
import java.util.Collection;
1111
import java.util.HashSet;
1212

13+
import static io.cucumber.guice.InjectorSourceFactory.createDefaultScenarioModuleInjectorSource;
14+
import static io.cucumber.guice.InjectorSourceFactory.instantiateUserSpecifiedInjectorSource;
1315
import static io.cucumber.guice.InjectorSourceFactory.loadInjectorSourceFromProperties;
1416
import static java.lang.String.format;
1517

@@ -29,6 +31,10 @@ public final class GuiceFactory implements ObjectFactory {
2931

3032
public GuiceFactory() {
3133
this.injectorSourceFromProperty = loadInjectorSourceFromProperties(CucumberProperties.create());
34+
// Eager init to allow for static binding prior to before all hooks
35+
if (this.injectorSourceFromProperty != null) {
36+
injector = instantiateUserSpecifiedInjectorSource(this.injectorSourceFromProperty).getInjector();
37+
}
3238
}
3339

3440
@Override
@@ -40,6 +46,9 @@ public boolean addClass(final Class<?> stepClass) {
4046
if (hasInjectorSource(stepClass)) {
4147
checkOnlyOneClassHasInjectorSource(stepClass);
4248
withInjectorSource = stepClass;
49+
// Eager init to allow for static binding prior to before all
50+
// hooks
51+
injector = instantiateUserSpecifiedInjectorSource(withInjectorSource).getInjector();
4352
}
4453
}
4554
stepClasses.add(stepClass);
@@ -69,9 +78,10 @@ void setInjector(Injector injector) {
6978
}
7079

7180
public void start() {
81+
// Last minute init. Neither properties not annotations provided an
82+
// injector source.
7283
if (injector == null) {
73-
injector = new InjectorSourceFactory(withInjectorSource).create()
74-
.getInjector();
84+
injector = createDefaultScenarioModuleInjectorSource().getInjector();
7585
}
7686
scenarioScope = injector.getInstance(ScenarioScope.class);
7787
scenarioScope.enterScope();

cucumber-guice/src/main/java/io/cucumber/guice/InjectorSourceFactory.java

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,29 +12,16 @@
1212
final class InjectorSourceFactory {
1313
private static final Logger log = LoggerFactory.getLogger(GuiceFactory.class);
1414
static final String GUICE_INJECTOR_SOURCE_KEY = "guice.injector-source";
15-
private final Class<?> injectorSourceClass;
1615

17-
InjectorSourceFactory(Class<?> injectorSourceClass) {
18-
this.injectorSourceClass = injectorSourceClass;
19-
}
20-
21-
InjectorSource create() {
22-
if (injectorSourceClass == null) {
23-
return createDefaultScenarioModuleInjectorSource();
24-
} else {
25-
return instantiateUserSpecifiedInjectorSource(injectorSourceClass);
26-
}
27-
}
28-
29-
private InjectorSource createDefaultScenarioModuleInjectorSource() {
16+
static InjectorSource createDefaultScenarioModuleInjectorSource() {
3017
return () -> Guice.createInjector(Stage.PRODUCTION, CucumberModules.createScenarioModule());
3118
}
3219

33-
private InjectorSource instantiateUserSpecifiedInjectorSource(Class<?> injectorSourceClass) {
20+
static InjectorSource instantiateUserSpecifiedInjectorSource(Class<?> injectorSourceClass) {
3421
try {
3522
return (InjectorSource) injectorSourceClass.getConstructor().newInstance();
3623
} catch (Exception e) {
37-
String message = format("Instantiation of '%s' failed. Check the caused by exception and ensure your" +
24+
String message = format("Instantiation of '%s' failed. Check the caused by exception and ensure your " +
3825
"InjectorSource implementation is accessible and has a public zero args constructor.",
3926
injectorSourceClass.getName());
4027
throw new InjectorSourceInstantiationFailed(message, e);
@@ -57,7 +44,7 @@ static Class<?> loadInjectorSourceFromProperties(Map<String, String> properties)
5744
try {
5845
return Class.forName(injectorSourceClassName, true, Thread.currentThread().getContextClassLoader());
5946
} catch (Exception e) {
60-
String message = format("Instantiation of '%s' failed. Check the caused by exception and ensure your" +
47+
String message = format("Instantiation of '%s' failed. Check the caused by exception and ensure your " +
6148
"InjectorSource implementation is accessible and has a public zero args constructor.",
6249
injectorSourceClassName);
6350
throw new InjectorSourceInstantiationFailed(message, e);

cucumber-guice/src/test/java/io/cucumber/guice/GuiceFactoryTest.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import com.google.inject.Guice;
66
import com.google.inject.Injector;
77
import com.google.inject.Module;
8+
import com.google.inject.Provides;
89
import com.google.inject.Scopes;
910
import com.google.inject.Stage;
1011
import io.cucumber.core.backend.CucumberBackendException;
@@ -14,6 +15,7 @@
1415
import io.cucumber.guice.integration.YourInjectorSource;
1516
import io.cucumber.guice.matcher.ElementsAreAllEqualMatcher;
1617
import io.cucumber.guice.matcher.ElementsAreAllUniqueMatcher;
18+
import jakarta.inject.Inject;
1719
import jakarta.inject.Singleton;
1820
import org.junit.jupiter.api.AfterEach;
1921
import org.junit.jupiter.api.Test;
@@ -23,6 +25,7 @@
2325
import java.util.List;
2426

2527
import static org.hamcrest.CoreMatchers.containsString;
28+
import static org.hamcrest.CoreMatchers.equalTo;
2629
import static org.hamcrest.CoreMatchers.is;
2730
import static org.hamcrest.CoreMatchers.notNullValue;
2831
import static org.hamcrest.MatcherAssert.assertThat;
@@ -242,6 +245,15 @@ void shouldThrowExceptionIfTwoDifferentInjectorSourcesAreFound() {
242245
assertThat("Unexpected exception message", actualThrown.getMessage(), is(exceptionMessage));
243246
}
244247

248+
@Test
249+
void shouldInjectStaticBeforeStart() {
250+
factory = new GuiceFactory();
251+
WithStaticFieldClass.property = null;
252+
factory.addClass(CucumberInjector.class);
253+
assertThat(WithStaticFieldClass.property, equalTo("Hello world"));
254+
255+
}
256+
245257
static class UnscopedClass {
246258

247259
}
@@ -263,5 +275,30 @@ static class BoundScenarioScopedClass {
263275
static class BoundSingletonClass {
264276

265277
}
278+
static class WithStaticFieldClass {
279+
280+
@Inject
281+
static String property;
282+
283+
}
284+
285+
public static class CucumberInjector implements InjectorSource {
286+
287+
@Override
288+
public Injector getInjector() {
289+
return Guice.createInjector(Stage.PRODUCTION, CucumberModules.createScenarioModule(), new AbstractModule() {
290+
@Override
291+
protected void configure() {
292+
requestStaticInjection(WithStaticFieldClass.class);
293+
}
294+
295+
@Singleton
296+
@Provides
297+
public String providesSomeString() {
298+
return "Hello world";
299+
}
300+
});
301+
}
302+
}
266303

267304
}

cucumber-guice/src/test/java/io/cucumber/guice/InjectorSourceFactoryTest.java

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import java.util.Map;
99

1010
import static io.cucumber.guice.InjectorSourceFactory.GUICE_INJECTOR_SOURCE_KEY;
11-
import static org.hamcrest.CoreMatchers.instanceOf;
11+
import static io.cucumber.guice.InjectorSourceFactory.instantiateUserSpecifiedInjectorSource;
1212
import static org.hamcrest.MatcherAssert.assertThat;
1313
import static org.hamcrest.core.Is.is;
1414
import static org.hamcrest.core.Is.isA;
@@ -18,12 +18,6 @@
1818

1919
class InjectorSourceFactoryTest {
2020

21-
@Test
22-
void createsDefaultInjectorSourceWhenGuiceModulePropertyIsNotSet() {
23-
InjectorSourceFactory injectorSourceFactory = new InjectorSourceFactory(null);
24-
assertThat(injectorSourceFactory.create(), is(instanceOf(InjectorSource.class)));
25-
}
26-
2721
@Test
2822
void instantiatesInjectorSourceByFullyQualifiedName() {
2923
Map<String, String> properties = new HashMap<>();
@@ -42,49 +36,43 @@ void failsToLoadNonExistantClass() {
4236
() -> InjectorSourceFactory.loadInjectorSourceFromProperties(properties));
4337
assertAll(
4438
() -> assertThat("Unexpected exception message", actualThrown.getMessage(), is(equalTo(
45-
"Instantiation of 'some.bogus.Class' failed. Check the caused by exception and ensure yourInjectorSource implementation is accessible and has a public zero args constructor."))),
39+
"Instantiation of 'some.bogus.Class' failed. Check the caused by exception and ensure your InjectorSource implementation is accessible and has a public zero args constructor."))),
4640
() -> assertThat("Unexpected exception cause class", actualThrown.getCause(),
4741
isA(ClassNotFoundException.class)));
4842
}
4943

5044
@Test
5145
void failsToInstantiateClassNotImplementingInjectorSource() {
52-
InjectorSourceFactory injectorSourceFactory = new InjectorSourceFactory(String.class);
53-
54-
Executable testMethod = injectorSourceFactory::create;
46+
Executable testMethod = () -> instantiateUserSpecifiedInjectorSource(String.class);
5547
InjectorSourceInstantiationFailed actualThrown = assertThrows(InjectorSourceInstantiationFailed.class,
5648
testMethod);
5749
assertAll(
5850
() -> assertThat("Unexpected exception message", actualThrown.getMessage(), is(equalTo(
59-
"Instantiation of 'java.lang.String' failed. Check the caused by exception and ensure yourInjectorSource implementation is accessible and has a public zero args constructor."))),
51+
"Instantiation of 'java.lang.String' failed. Check the caused by exception and ensure your InjectorSource implementation is accessible and has a public zero args constructor."))),
6052
() -> assertThat("Unexpected exception cause class", actualThrown.getCause(),
6153
isA(ClassCastException.class)));
6254
}
6355

6456
@Test
6557
void failsToInstantiateClassWithPrivateConstructor() {
66-
InjectorSourceFactory injectorSourceFactory = new InjectorSourceFactory(PrivateConstructor.class);
67-
68-
Executable testMethod = injectorSourceFactory::create;
58+
Executable testMethod = () -> instantiateUserSpecifiedInjectorSource(PrivateConstructor.class);
6959
InjectorSourceInstantiationFailed actualThrown = assertThrows(InjectorSourceInstantiationFailed.class,
7060
testMethod);
7161
assertAll(
7262
() -> assertThat("Unexpected exception message", actualThrown.getMessage(), is(equalTo(
73-
"Instantiation of 'io.cucumber.guice.InjectorSourceFactoryTest$PrivateConstructor' failed. Check the caused by exception and ensure yourInjectorSource implementation is accessible and has a public zero args constructor."))),
63+
"Instantiation of 'io.cucumber.guice.InjectorSourceFactoryTest$PrivateConstructor' failed. Check the caused by exception and ensure your InjectorSource implementation is accessible and has a public zero args constructor."))),
7464
() -> assertThat("Unexpected exception cause class", actualThrown.getCause(),
7565
isA(NoSuchMethodException.class)));
7666
}
7767

7868
@Test
7969
void failsToInstantiateClassWithNoDefaultConstructor() {
80-
InjectorSourceFactory injectorSourceFactory = new InjectorSourceFactory(NoDefaultConstructor.class);
81-
82-
Executable testMethod = injectorSourceFactory::create;
70+
Executable testMethod = () -> instantiateUserSpecifiedInjectorSource(NoDefaultConstructor.class);
8371
InjectorSourceInstantiationFailed actualThrown = assertThrows(InjectorSourceInstantiationFailed.class,
8472
testMethod);
8573
assertAll(
8674
() -> assertThat("Unexpected exception message", actualThrown.getMessage(), is(equalTo(
87-
"Instantiation of 'io.cucumber.guice.InjectorSourceFactoryTest$NoDefaultConstructor' failed. Check the caused by exception and ensure yourInjectorSource implementation is accessible and has a public zero args constructor."))),
75+
"Instantiation of 'io.cucumber.guice.InjectorSourceFactoryTest$NoDefaultConstructor' failed. Check the caused by exception and ensure your InjectorSource implementation is accessible and has a public zero args constructor."))),
8876
() -> assertThat("Unexpected exception cause class", actualThrown.getCause(),
8977
isA(NoSuchMethodException.class)));
9078
}

0 commit comments

Comments
 (0)