diff --git a/core/src/main/java/io/cucumber/core/options/CucumberOptionsAnnotationParser.java b/core/src/main/java/io/cucumber/core/options/CucumberOptionsAnnotationParser.java index 63c9ea6915..a3b61b4e04 100644 --- a/core/src/main/java/io/cucumber/core/options/CucumberOptionsAnnotationParser.java +++ b/core/src/main/java/io/cucumber/core/options/CucumberOptionsAnnotationParser.java @@ -1,6 +1,7 @@ package io.cucumber.core.options; import io.cucumber.core.snippets.SnippetType; +import io.cucumber.core.backend.ObjectFactory; import io.cucumber.core.exception.CucumberException; import io.cucumber.core.io.MultiLoader; import io.cucumber.core.io.ResourceLoader; @@ -50,6 +51,7 @@ public RuntimeOptionsBuilder parse(Class clazz) { addSnippets(options, args); addGlue(options, args); addFeatures(options, args); + addObjectFactory(options, args); } } addDefaultFeaturePathIfNoFeaturePathIsSpecified(args, clazz); @@ -146,13 +148,18 @@ private void addDefaultGlueIfNoOverridingGlueIsSpecified(RuntimeOptionsBuilder a } } - private void addStrict(CucumberOptions options, RuntimeOptionsBuilder args) { if (options.strict()) { args.setStrict(true); } } + private void addObjectFactory(CucumberOptions options, RuntimeOptionsBuilder args) { + if (options.objectFactory() != null) { + args.setObjectFactoryClass(options.objectFactory()); + } + } + private static String packagePath(Class clazz) { String packageName = packageName(clazz); @@ -204,5 +211,7 @@ public interface CucumberOptions { String[] name(); SnippetType snippets(); + + Class objectFactory(); } } diff --git a/core/src/main/java/io/cucumber/core/options/CucumberProperties.java b/core/src/main/java/io/cucumber/core/options/CucumberProperties.java index 587539c15c..aec262e7f7 100644 --- a/core/src/main/java/io/cucumber/core/options/CucumberProperties.java +++ b/core/src/main/java/io/cucumber/core/options/CucumberProperties.java @@ -121,4 +121,4 @@ public String get(Object key) { } } -} \ No newline at end of file +} diff --git a/core/src/main/java/io/cucumber/core/options/CucumberPropertiesParser.java b/core/src/main/java/io/cucumber/core/options/CucumberPropertiesParser.java index 93aa7b928f..97a4d7da9b 100644 --- a/core/src/main/java/io/cucumber/core/options/CucumberPropertiesParser.java +++ b/core/src/main/java/io/cucumber/core/options/CucumberPropertiesParser.java @@ -38,7 +38,7 @@ public RuntimeOptionsBuilder parse(Map properties) { String cucumberObjectFactory = properties.get(CUCUMBER_OBJECT_FACTORY_PROPERTY_NAME); if (cucumberObjectFactory != null) { - Class objectFactoryClass = parse(cucumberObjectFactory); + Class objectFactoryClass = parseObjectFactory(cucumberObjectFactory); builder.setObjectFactoryClass(objectFactoryClass); } @@ -46,7 +46,7 @@ public RuntimeOptionsBuilder parse(Map properties) { } @SuppressWarnings("unchecked") - private Class parse(String cucumberObjectFactory) { + static Class parseObjectFactory(String cucumberObjectFactory) { Class objectFactoryClass; try { objectFactoryClass = Class.forName(cucumberObjectFactory); diff --git a/core/src/main/java/io/cucumber/core/options/RuntimeOptionsParser.java b/core/src/main/java/io/cucumber/core/options/RuntimeOptionsParser.java index fd65d0a451..dba3e8b63d 100644 --- a/core/src/main/java/io/cucumber/core/options/RuntimeOptionsParser.java +++ b/core/src/main/java/io/cucumber/core/options/RuntimeOptionsParser.java @@ -105,6 +105,9 @@ RuntimeOptionsBuilder parse(List args) { throw new CucumberException("--count must be > 0"); } parsedOptions.setCount(count); + } else if (arg.equals("--object-factory")) { + String objectFactoryClassName = args.remove(0); + parsedOptions.setObjectFactoryClass(CucumberPropertiesParser.parseObjectFactory(objectFactoryClassName)); } else if (arg.startsWith("-")) { printUsage(); throw new CucumberException("Unknown option: " + arg); diff --git a/core/src/main/java/io/cucumber/core/snippets/SnippetType.java b/core/src/main/java/io/cucumber/core/snippets/SnippetType.java index 5c4f7384bc..813a617622 100644 --- a/core/src/main/java/io/cucumber/core/snippets/SnippetType.java +++ b/core/src/main/java/io/cucumber/core/snippets/SnippetType.java @@ -1,8 +1,5 @@ package io.cucumber.core.snippets; -import org.apiguardian.api.API; - -@API(status = API.Status.STABLE) public enum SnippetType { UNDERSCORE(new UnderscoreJoiner()), CAMELCASE(new CamelCaseJoiner()); diff --git a/core/src/main/resources/io/cucumber/core/options/USAGE.txt b/core/src/main/resources/io/cucumber/core/options/USAGE.txt index 82c6a3ddbd..70107638c4 100644 --- a/core/src/main/resources/io/cucumber/core/options/USAGE.txt +++ b/core/src/main/resources/io/cucumber/core/options/USAGE.txt @@ -1,4 +1,4 @@ -Usage: java cucumber.api.cli.Main [options] [ [DIR|DIR URI] | [ [FILE|FILE URI][:LINE]* ] | @[FILE|FILE URI] ]+ +Usage: java io.cucumber.core.cli.Main [options] [ [DIR|DIR URI] | [ [FILE|FILE URI][:LINE]* ] | @[FILE|FILE URI] ]+ Options: @@ -50,6 +50,12 @@ Options: --count Number of scenarios to be executed. If not specified all scenarios are run. + --object-factory CLASSNAME Uses the class specified by CLASSNAME as + object factory. Be aware that the class is + loaded through a service loader and therefore + also needs to be specified in: + META-INF/services/io.cucumber.core.backend.ObjectFactory + Feature path examples: Load the files with the extension ".feature" diff --git a/core/src/test/java/io/cucumber/core/options/CucumberOptions.java b/core/src/test/java/io/cucumber/core/options/CucumberOptions.java index 126b75f8b8..9d560b98d6 100644 --- a/core/src/test/java/io/cucumber/core/options/CucumberOptions.java +++ b/core/src/test/java/io/cucumber/core/options/CucumberOptions.java @@ -114,6 +114,13 @@ */ SnippetType snippets() default SnippetType.UNDERSCORE; + /** + * A custom ObjectFactory. + * + * @return The class of the custim ObjectFactory to use. + */ + Class objectFactory() default NoObjectFactory.class; + /** * Pass options to the JUnit runner. * diff --git a/core/src/test/java/io/cucumber/core/options/CucumberOptionsAnnotationParserTest.java b/core/src/test/java/io/cucumber/core/options/CucumberOptionsAnnotationParserTest.java index 200059ce6a..b5487fed4b 100644 --- a/core/src/test/java/io/cucumber/core/options/CucumberOptionsAnnotationParserTest.java +++ b/core/src/test/java/io/cucumber/core/options/CucumberOptionsAnnotationParserTest.java @@ -2,6 +2,7 @@ import io.cucumber.core.plugin.Plugin; import io.cucumber.core.snippets.SnippetType; +import io.cucumber.core.backend.ObjectFactory; import io.cucumber.core.exception.CucumberException; import io.cucumber.core.plugin.PluginFactory; import io.cucumber.core.plugin.Plugins; @@ -19,6 +20,7 @@ import static org.hamcrest.collection.IsIterableContainingInOrder.contains; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; @@ -48,6 +50,7 @@ public void create_without_options() { .addDefaultFormatterIfNotPresent() .build(); assertFalse(runtimeOptions.isStrict()); + assertNull(runtimeOptions.getObjectFactoryClass()); assertThat(runtimeOptions.getFeaturePaths(), contains(uri("classpath:io/cucumber/core/options"))); assertThat(runtimeOptions.getGlue(), contains(uri("classpath:io/cucumber/core/options"))); Plugins plugins = new Plugins(new PluginFactory(), runtimeOptions); @@ -100,6 +103,12 @@ public void create_with_multiple_names() { assertEquals("name2", getRegexpPattern(iterator.next())); } + @Test + public void testObjectFactory() { + RuntimeOptions runtimeOptions = parser().parse(ClassWithCustomObjectFactory.class).build(); + assertEquals(TestObjectFactory.class, runtimeOptions.getObjectFactoryClass()); + } + @Test public void create_with_snippets() { RuntimeOptions runtimeOptions = parser().parse(Snippets.class).build(); @@ -238,6 +247,11 @@ private static class BaseClassWithMonoChromeFalse { // empty } + @CucumberOptions(objectFactory = TestObjectFactory.class) + private static class ClassWithCustomObjectFactory { + // empty + } + @CucumberOptions(plugin = "io.cucumber.core.plugin.AnyStepDefinitionReporter") private static class ClassWithNoFormatterPlugin { // empty @@ -336,6 +350,10 @@ public SnippetType snippets() { return annotation.snippets(); } + @Override + public Class objectFactory() { + return (annotation.objectFactory() == NoObjectFactory.class) ? null : annotation.objectFactory(); + } } private static class CoreCucumberOptionsProvider implements CucumberOptionsAnnotationParser.OptionsProvider { @@ -348,4 +366,24 @@ public CucumberOptionsAnnotationParser.CucumberOptions getOptions(Class clazz return new CoreCucumberOptions(annotation); } } + + private static final class TestObjectFactory implements ObjectFactory { + + @Override + public boolean addClass(Class glueClass) { + return false; + } + + @Override + public T getInstance(Class glueClass) { + return null; + } + + @Override + public void start() {} + + @Override + public void stop() {} + + } } diff --git a/core/src/test/java/io/cucumber/core/options/CucumberPropertiesParserTest.java b/core/src/test/java/io/cucumber/core/options/CucumberPropertiesParserTest.java index c016283a02..996f5eef89 100644 --- a/core/src/test/java/io/cucumber/core/options/CucumberPropertiesParserTest.java +++ b/core/src/test/java/io/cucumber/core/options/CucumberPropertiesParserTest.java @@ -23,7 +23,6 @@ public void should_parse_cucumber_options(){ assertThat(options.getGlue(), equalTo(singletonList(URI.create("classpath:com/example")))); } - @Test public void should_parse_cucumber_object_factory(){ properties.put(Constants.CUCUMBER_OBJECT_FACTORY_PROPERTY_NAME, CustomObjectFactory.class.getName()); @@ -31,8 +30,8 @@ public void should_parse_cucumber_object_factory(){ assertThat(options.getObjectFactoryClass(), equalTo(CustomObjectFactory.class)); } - private static final class CustomObjectFactory implements ObjectFactory { + private static final class CustomObjectFactory implements ObjectFactory { @Override public boolean addClass(Class glueClass) { return false; @@ -53,5 +52,4 @@ public void stop() { } } - -} \ No newline at end of file +} diff --git a/core/src/test/java/io/cucumber/core/options/NoObjectFactory.java b/core/src/test/java/io/cucumber/core/options/NoObjectFactory.java new file mode 100644 index 0000000000..45d8ebcfbe --- /dev/null +++ b/core/src/test/java/io/cucumber/core/options/NoObjectFactory.java @@ -0,0 +1,30 @@ +package io.cucumber.core.options; + +import io.cucumber.core.backend.ObjectFactory; + +/** + * This object factory does nothing. It is solely needed for marking purposes. + */ +final class NoObjectFactory implements ObjectFactory { + + private NoObjectFactory() { + // No need for instantiation + } + + @Override + public boolean addClass(Class glueClass) { + return false; + } + + @Override + public T getInstance(Class glueClass) { + return null; + } + + @Override + public void start() {} + + @Override + public void stop() {} + +} diff --git a/core/src/test/java/io/cucumber/core/options/RuntimeOptionsParserTest.java b/core/src/test/java/io/cucumber/core/options/RuntimeOptionsParserTest.java new file mode 100644 index 0000000000..d24d51cdab --- /dev/null +++ b/core/src/test/java/io/cucumber/core/options/RuntimeOptionsParserTest.java @@ -0,0 +1,52 @@ +package io.cucumber.core.options; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.Arrays; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import io.cucumber.core.backend.ObjectFactory; +import io.cucumber.core.feature.RerunLoader; +import io.cucumber.core.io.MultiLoader; + +final class RuntimeOptionsParserTest { + + private RuntimeOptionsParser parser; + + @BeforeEach + void setUp() throws Exception { + this.parser = new RuntimeOptionsParser(new RerunLoader( new MultiLoader(this.getClass().getClassLoader()))); + } + + @Test + void testParseWithObjectFactoryArgument() { + RuntimeOptionsBuilder optionsBuilder = this.parser.parse(Arrays.asList("--object-factory", TestObjectFactory.class.getName())); + assertNotNull(optionsBuilder); + RuntimeOptions options = optionsBuilder.build(); + assertNotNull(options); + assertEquals(TestObjectFactory.class, options.getObjectFactoryClass()); + } + + + private static final class TestObjectFactory implements ObjectFactory { + + @Override + public boolean addClass(Class glueClass) { + return false; + } + + @Override + public T getInstance(Class glueClass) { + return null; + } + + @Override + public void start() {} + + @Override + public void stop() {} + + } +} diff --git a/junit/src/main/java/io/cucumber/junit/CucumberOptions.java b/junit/src/main/java/io/cucumber/junit/CucumberOptions.java index f928c6150d..7cca11be67 100644 --- a/junit/src/main/java/io/cucumber/junit/CucumberOptions.java +++ b/junit/src/main/java/io/cucumber/junit/CucumberOptions.java @@ -117,6 +117,16 @@ */ boolean stepNotifications() default false; + /** + * Specify a custom ObjectFactory. + *

+ * In case a custom ObjectFactory is needed, the class can be specified here. + * A custom ObjectFactory might be needed when more granular control is needed + * over the dependency injection mechanism. + */ + Class objectFactory() default NoObjectFactory.class; + + enum SnippetType { UNDERSCORE, CAMELCASE } diff --git a/junit/src/main/java/io/cucumber/junit/JUnitCucumberOptionsProvider.java b/junit/src/main/java/io/cucumber/junit/JUnitCucumberOptionsProvider.java index 8cc8db2100..2772915a3c 100644 --- a/junit/src/main/java/io/cucumber/junit/JUnitCucumberOptionsProvider.java +++ b/junit/src/main/java/io/cucumber/junit/JUnitCucumberOptionsProvider.java @@ -1,6 +1,7 @@ package io.cucumber.junit; import io.cucumber.core.snippets.SnippetType; +import io.cucumber.core.backend.ObjectFactory; import io.cucumber.core.options.CucumberOptionsAnnotationParser; final class JUnitCucumberOptionsProvider implements CucumberOptionsAnnotationParser.OptionsProvider { @@ -68,13 +69,18 @@ public String[] name() { @Override public SnippetType snippets() { switch (annotation.snippets()) { - case UNDERSCORE: - return SnippetType.UNDERSCORE; - case CAMELCASE: - return SnippetType.CAMELCASE; - default: - throw new IllegalArgumentException("" + annotation.snippets()); + case UNDERSCORE: + return SnippetType.UNDERSCORE; + case CAMELCASE: + return SnippetType.CAMELCASE; + default: + throw new IllegalArgumentException("" + annotation.snippets()); } } + + @Override + public Class objectFactory() { + return (annotation.objectFactory() == NoObjectFactory.class) ? null : annotation.objectFactory(); + } } } diff --git a/junit/src/main/java/io/cucumber/junit/NoObjectFactory.java b/junit/src/main/java/io/cucumber/junit/NoObjectFactory.java new file mode 100644 index 0000000000..c286963434 --- /dev/null +++ b/junit/src/main/java/io/cucumber/junit/NoObjectFactory.java @@ -0,0 +1,30 @@ +package io.cucumber.junit; + +import io.cucumber.core.backend.ObjectFactory; + +/** + * This object factory does nothing. It is solely needed for marking purposes. + */ +final class NoObjectFactory implements ObjectFactory { + + private NoObjectFactory() { + // No need for instantiation + } + + @Override + public boolean addClass(Class glueClass) { + return false; + } + + @Override + public T getInstance(Class glueClass) { + return null; + } + + @Override + public void start() {} + + @Override + public void stop() {} + +} diff --git a/junit/src/test/java/io/cucumber/junit/JUnitCucumberOptionsProviderTest.java b/junit/src/test/java/io/cucumber/junit/JUnitCucumberOptionsProviderTest.java new file mode 100644 index 0000000000..1f2748c5a2 --- /dev/null +++ b/junit/src/test/java/io/cucumber/junit/JUnitCucumberOptionsProviderTest.java @@ -0,0 +1,57 @@ +package io.cucumber.junit; + + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import io.cucumber.core.backend.ObjectFactory; + + +final class JUnitCucumberOptionsProviderTest { + + private JUnitCucumberOptionsProvider optionsProvider; + + @BeforeEach + void setUp() throws Exception { + this.optionsProvider = new JUnitCucumberOptionsProvider(); + } + + @Test + void testObjectFactoryWhenNotSpecified() { + io.cucumber.core.options.CucumberOptionsAnnotationParser.CucumberOptions options = this.optionsProvider.getOptions(ClassWithDefault.class); + assertNull(options.objectFactory()); + } + + @Test + void testObjectFactory() { + io.cucumber.core.options.CucumberOptionsAnnotationParser.CucumberOptions options = this.optionsProvider.getOptions(ClassWithCustomObjectFactory.class); + assertNotNull(options.objectFactory()); + assertEquals(TestObjectFactory.class, options.objectFactory()); + } + + @CucumberOptions() + private static final class ClassWithDefault {} + + @CucumberOptions(objectFactory = TestObjectFactory.class) + private static final class ClassWithCustomObjectFactory {} + + private static final class TestObjectFactory implements ObjectFactory { + @Override + public boolean addClass(Class glueClass) { + return false; + } + + @Override + public T getInstance(Class glueClass) { + return null; + } + + @Override + public void start() {} + + @Override + public void stop() {} + } +} diff --git a/testng/src/main/java/io/cucumber/testng/CucumberOptions.java b/testng/src/main/java/io/cucumber/testng/CucumberOptions.java index e1e41cd486..9ebe07a08d 100644 --- a/testng/src/main/java/io/cucumber/testng/CucumberOptions.java +++ b/testng/src/main/java/io/cucumber/testng/CucumberOptions.java @@ -95,6 +95,16 @@ */ SnippetType snippets() default SnippetType.UNDERSCORE; + /** + * Specify a custom ObjectFactory. + *

+ * In case a custom ObjectFactory is needed, the class can be specified here. + * A custom ObjectFactory might be needed when more granular control is needed + * over the dependency injection mechanism. + */ + Class objectFactory() default NoObjectFactory.class; + + enum SnippetType { UNDERSCORE, CAMELCASE } diff --git a/testng/src/main/java/io/cucumber/testng/NoObjectFactory.java b/testng/src/main/java/io/cucumber/testng/NoObjectFactory.java new file mode 100644 index 0000000000..5dbea2ddfb --- /dev/null +++ b/testng/src/main/java/io/cucumber/testng/NoObjectFactory.java @@ -0,0 +1,30 @@ +package io.cucumber.testng; + +import io.cucumber.core.backend.ObjectFactory; + +/** + * This object factory does nothing. It is solely needed for marking purposes. + */ +final class NoObjectFactory implements ObjectFactory { + + private NoObjectFactory() { + // No need for instantiation + } + + @Override + public boolean addClass(Class glueClass) { + return false; + } + + @Override + public T getInstance(Class glueClass) { + return null; + } + + @Override + public void start() {} + + @Override + public void stop() {} + +} diff --git a/testng/src/main/java/io/cucumber/testng/TestNGCucumberOptionsProvider.java b/testng/src/main/java/io/cucumber/testng/TestNGCucumberOptionsProvider.java index 159da70f1e..45cc41595a 100644 --- a/testng/src/main/java/io/cucumber/testng/TestNGCucumberOptionsProvider.java +++ b/testng/src/main/java/io/cucumber/testng/TestNGCucumberOptionsProvider.java @@ -1,6 +1,7 @@ package io.cucumber.testng; import io.cucumber.core.snippets.SnippetType; +import io.cucumber.core.backend.ObjectFactory; import io.cucumber.core.options.CucumberOptionsAnnotationParser; class TestNGCucumberOptionsProvider implements CucumberOptionsAnnotationParser.OptionsProvider { @@ -10,13 +11,13 @@ public CucumberOptionsAnnotationParser.CucumberOptions getOptions(Class clazz if (annotation == null) { return null; } - return new JunitCucumberOptions(annotation); + return new TestNGCucumberOptions(annotation); } - private static class JunitCucumberOptions implements CucumberOptionsAnnotationParser.CucumberOptions { + private static class TestNGCucumberOptions implements CucumberOptionsAnnotationParser.CucumberOptions { private final CucumberOptions annotation; - JunitCucumberOptions(CucumberOptions annotation) { + TestNGCucumberOptions(CucumberOptions annotation) { this.annotation = annotation; } @@ -68,13 +69,18 @@ public String[] name() { @Override public SnippetType snippets() { switch (annotation.snippets()) { - case UNDERSCORE: - return SnippetType.UNDERSCORE; - case CAMELCASE: - return SnippetType.CAMELCASE; - default: - throw new IllegalArgumentException("" + annotation.snippets()); + case UNDERSCORE: + return SnippetType.UNDERSCORE; + case CAMELCASE: + return SnippetType.CAMELCASE; + default: + throw new IllegalArgumentException("" + annotation.snippets()); } } + + @Override + public Class objectFactory() { + return (annotation.objectFactory() == NoObjectFactory.class) ? null : annotation.objectFactory(); + } } } diff --git a/testng/src/test/java/io/cucumber/testng/TestNGCucumberOptionsProviderTest.java b/testng/src/test/java/io/cucumber/testng/TestNGCucumberOptionsProviderTest.java new file mode 100644 index 0000000000..a2d8fdfd31 --- /dev/null +++ b/testng/src/test/java/io/cucumber/testng/TestNGCucumberOptionsProviderTest.java @@ -0,0 +1,56 @@ +package io.cucumber.testng; + + +import static org.testng.Assert.*; + +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; + +import io.cucumber.core.backend.ObjectFactory; + + +final class TestNGCucumberOptionsProviderTest { + + private TestNGCucumberOptionsProvider optionsProvider; + + @BeforeTest + void setUp() throws Exception { + this.optionsProvider = new TestNGCucumberOptionsProvider(); + } + @Test + void testObjectFactoryWhenNotSpecified() { + io.cucumber.core.options.CucumberOptionsAnnotationParser.CucumberOptions options = this.optionsProvider.getOptions(ClassWithDefault.class); + assertNull(options.objectFactory()); + } + + @Test + void testObjectFactory() { + io.cucumber.core.options.CucumberOptionsAnnotationParser.CucumberOptions options = this.optionsProvider.getOptions(ClassWithCustomObjectFactory.class); + assertNotNull(options.objectFactory()); + assertEquals(TestObjectFactory.class, options.objectFactory()); + } + + @CucumberOptions() + private static final class ClassWithDefault {} + + @CucumberOptions(objectFactory = TestObjectFactory.class) + private static final class ClassWithCustomObjectFactory {} + + private static final class TestObjectFactory implements ObjectFactory { + @Override + public boolean addClass(Class glueClass) { + return false; + } + + @Override + public T getInstance(Class glueClass) { + return null; + } + + @Override + public void start() {} + + @Override + public void stop() {} + } +}