Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Step definition constructor injection #37

Merged
merged 7 commits into from

3 participants

@jbandi

Registering classes that are constructor arguments of step definition classes into the object-factory. This enables injecting pure pojos into step definition classes.

@aslakhellesoy aslakhellesoy merged commit d9de90e into cucumber:master
@mattnathan

This change introduces a number of problems when using Guava to inject dependencies.

The error I get is:

WARNING: Cucumber/Guice could not create instance for byte[]:
Guice configuration errors:

1) No implementation for byte[] was bound.
  while locating byte[]

1 error
WARNING: Cucumber/Guice could not create instance for char[]:
Guice configuration errors:

1) No implementation for char[] was bound.
  while locating char[]

1 error
WARNING: Cucumber/Guice could not create instance for int:
Guice configuration errors:

1) Could not find a suitable constructor in java.lang.Integer. Classes must have either one (and only one) constructor annotated with @Inject or a zero-argument constructor that is not private.
  at java.lang.Integer.class(Integer.java:37)
  while locating java.lang.Integer

1 error
WARNING: Cucumber/Guice could not create instance for int[]:
Guice configuration errors:

1) No implementation for int[] was bound.
  while locating int[]

1 error
WARNING: Cucumber/Guice could not create instance for java.lang.CharSequence:
Guice configuration errors:

1) No implementation for java.lang.CharSequence was bound.
  while locating java.lang.CharSequence

1 error
WARNING: Cucumber/Guice could not create instance for java.nio.charset.Charset:
Guice configuration errors:

1) No implementation for java.nio.charset.Charset was bound.
  while locating java.nio.charset.Charset

1 error

The reason for the error is the recursive addition of all constructor arguments to the objectFactory. This should not happen. If anything the addition of constructor arguments should be an implementation detail of whichever ObjectFactory you are using.

@mattnathan

Should note that there is a workaround: where I have a class being injected into a constructor I could replace it with an interface and specify a binding in my Guice module. I'd prefer not to do this though.

@aslakhellesoy

@mattnathan please open a new ticket for this. This pull request is closed, and cannot be reopened.

@mattnathan

Will do

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Sep 1, 2011
Commits on Sep 3, 2011
Commits on Sep 5, 2011
  1. removing ejb3 example

    jbandi authored
Commits on Sep 7, 2011
  1. Merging from upstream

    jbandi authored
  2. preventing recursion when registering constructor arguments of step d…

    jbandi authored
    …efinition classes in object factory
  3. preventing recursion when registering constructor arguments of step d…

    jbandi authored
    …efinition classes in object factory
This page is out of date. Refresh to see the latest.
View
25 java/src/main/java/cucumber/runtime/java/JavaBackend.java
@@ -10,8 +10,11 @@
import gherkin.formatter.model.Step;
import java.lang.annotation.Annotation;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
import java.util.regex.Pattern;
@@ -20,6 +23,7 @@
private final List<StepDefinition> stepDefinitions = new ArrayList<StepDefinition>();
private final List<HookDefinition> beforeHooks = new ArrayList<HookDefinition>();
private final List<HookDefinition> afterHooks = new ArrayList<HookDefinition>();
+ private final HashSet<Class> stepDefinitionClasses = new HashSet<Class>();
public JavaBackend(List<String> packagePrefixes) {
this.objectFactory = Resources.instantiateExactlyOneSubclass(ObjectFactory.class, "cucumber.runtime", new Class[0], new Object[0]);
@@ -51,7 +55,7 @@ public String getSnippet(Step step) {
void addStepDefinition(Pattern pattern, Method method) {
Class<?> clazz = method.getDeclaringClass();
- objectFactory.addClass(clazz);
+ registerClassInObjectFactory(clazz);
stepDefinitions.add(new JavaStepDefinition(pattern, method, objectFactory));
}
@@ -71,6 +75,25 @@ void registerHook(Annotation annotation, Method method) {
}
}
+ private void registerClassInObjectFactory(Class<?> clazz) {
+ if (!stepDefinitionClasses.contains(clazz))
+ {
+ objectFactory.addClass(clazz);
+ stepDefinitionClasses.add(clazz);
+ addConstructorDependencies(clazz);
+ }
+ }
+
+ private void addConstructorDependencies(Class<?> clazz) {
+ for (Constructor constructor : clazz.getConstructors())
+ {
+ for(Class paramClazz : constructor.getParameterTypes())
+ {
+ registerClassInObjectFactory(paramClazz);
+ }
+ }
+ }
+
@Override
public List<HookDefinition> getBeforeHooks() {
return beforeHooks;
View
85 java/src/test/java/cucumber/runtime/java/JavaStepDefinitionDependencyInjectionTest.java
@@ -0,0 +1,85 @@
+package cucumber.runtime.java;
+
+import cucumber.annotation.en.Given;
+import cucumber.runtime.StepDefinition;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+public class JavaStepDefinitionDependencyInjectionTest {
+
+ private static final Method GIVEN;
+ private static final Method OTHER_GIVEN;
+
+ static {
+ try {
+ GIVEN = Steps.class.getMethod("givenStep");
+ OTHER_GIVEN = OtherSteps.class.getMethod("givenStep");
+ } catch (NoSuchMethodException e) {
+ throw new InternalError("dang");
+ }
+ }
+
+ private ObjectFactory mockObjectFactory = mock(ObjectFactory.class);
+ private JavaBackend backend = new JavaBackend(mockObjectFactory);
+
+ @Test
+ public void constructor_arguments_get_registered() {
+ backend.addStepDefinition(Pattern.compile("whatever"), GIVEN);
+ verify(mockObjectFactory).addClass(Steps.class);
+ verify(mockObjectFactory).addClass(StepContext1.class);
+ verify(mockObjectFactory).addClass(StepContext2.class);
+ }
+
+ @Test
+ public void constructor_arguments_get_registered_exactly_once() {
+ backend.addStepDefinition(Pattern.compile("whatever"), OTHER_GIVEN);
+ verify(mockObjectFactory, times(1)).addClass(OtherSteps.class);
+ verify(mockObjectFactory, times(1)).addClass(StepContext3.class);
+ verify(mockObjectFactory, times(1)).addClass(StepContext4.class);
+ }
+
+ public class Steps {
+
+ public Steps(StepContext1 context1, StepContext2 context2) {
+ }
+
+ @Given("whatever")
+ public void givenStep() {
+ }
+ }
+
+ public class StepContext1 {
+ }
+
+ public class StepContext2 {
+ }
+
+ public class OtherSteps {
+
+ public OtherSteps(StepContext3 context3, StepContext4 context4) {
+ }
+
+ @Given("whatever")
+ public void givenStep() {
+ }
+ }
+
+ public class StepContext3 {
+ public StepContext3(StepContext4 context4) {
+ }
+ }
+
+ public class StepContext4 {
+ public StepContext4(StepContext3 context3) {
+ }
+ }
+}
Something went wrong with that request. Please try again.