Skip to content

ObjectFactory in @CucumberOptions is never used. #2424

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
TwoClocks opened this issue Nov 25, 2021 · 5 comments
Closed

ObjectFactory in @CucumberOptions is never used. #2424

TwoClocks opened this issue Nov 25, 2021 · 5 comments

Comments

@TwoClocks
Copy link

TwoClocks commented Nov 25, 2021

While you can set ObjectFactory it is never used. You can set it to a class that isn't known by the service loader. And everything works fine.

When loadObjectFactory() is called, this.options.getObjectFactoryClass() always returns null .
at : io.cucumber.core.runtime.ObjectFactoryServiceLoader.loadObjectFactory(ObjectFactoryServiceLoader.java:41)

If you set ObjectFactory the loader class just sets it to the first service returned w/o checking if the types match.

If the service factory has 0 items, it works (default is set). If it has exactly 1 item, that's the factory used. Any other situation and it blows up.

I think the code that calls loadObjectFactory() is called before the options are parsed, which would explain why this.options.getObjectFactoryClass() always returns null. But that's just a guess. I dug around for a while, but the startup code is hard for me to follow.

This bug exists in 7.0.0, and 6.8.1. Those are the only two versions I tried.
Using junit 4.

As a separate issue: Could DefaultFactory be public/available? If I have a custom ObjectFactory, but I don't want to use it for every set of tests, I need to write my own "default" for the other ones.
Maybe if the iterator length > 1, ObjectFactory should be mandatory in every case to avoid confusion?

@mpkorstanje
Copy link
Contributor

The default object factory will be available once #2400 is released in 7.1. No released date planned at this time.

I can't reproduce your problem from the information you've provided. What I suspect that might happen is that you are not running Cucumber via JUnit but rather through IDEA which calls the command line runner.

If you're looking to debug further then when using JUnit 4 the options parsing happens here, before anything else is done:

public Cucumber(Class<?> clazz) throws InitializationError {
super(clazz);
Assertions.assertNoCucumberAnnotatedMethods(clazz);
// Parse the options early to provide fast feedback about invalid
// options
RuntimeOptions propertiesFileOptions = new CucumberPropertiesParser()
.parse(CucumberProperties.fromPropertiesFile())
.build();
RuntimeOptions annotationOptions = new CucumberOptionsAnnotationParser()
.withOptionsProvider(new JUnitCucumberOptionsProvider())
.parse(clazz)
.build(propertiesFileOptions);
RuntimeOptions environmentOptions = new CucumberPropertiesParser()
.parse(CucumberProperties.fromEnvironment())
.build(annotationOptions);
RuntimeOptions runtimeOptions = new CucumberPropertiesParser()
.parse(CucumberProperties.fromSystemProperties())
.enablePublishPlugin()
.build(environmentOptions);
// Next parse the junit options
JUnitOptions junitPropertiesFileOptions = new JUnitOptionsParser()
.parse(CucumberProperties.fromPropertiesFile())
.build();
JUnitOptions junitAnnotationOptions = new JUnitOptionsParser()
.parse(clazz)
.build(junitPropertiesFileOptions);
JUnitOptions junitEnvironmentOptions = new JUnitOptionsParser()
.parse(CucumberProperties.fromEnvironment())
.build(junitAnnotationOptions);
JUnitOptions junitOptions = new JUnitOptionsParser()
.parse(CucumberProperties.fromSystemProperties())
.build(junitEnvironmentOptions);

And for the CLI:

public static byte run(String[] argv, ClassLoader classLoader) {
RuntimeOptions propertiesFileOptions = new CucumberPropertiesParser()
.parse(CucumberProperties.fromPropertiesFile())
.build();
RuntimeOptions environmentOptions = new CucumberPropertiesParser()
.parse(CucumberProperties.fromEnvironment())
.build(propertiesFileOptions);
RuntimeOptions systemOptions = new CucumberPropertiesParser()
.parse(CucumberProperties.fromSystemProperties())
.build(environmentOptions);
CommandlineOptionsParser commandlineOptionsParser = new CommandlineOptionsParser(System.out);
RuntimeOptions runtimeOptions = commandlineOptionsParser
.parse(argv)
.addDefaultGlueIfAbsent()
.addDefaultFeaturePathIfAbsent()
.addDefaultSummaryPrinterIfNotDisabled()
.enablePublishPlugin()
.build(systemOptions);

@TwoClocks
Copy link
Author

You are correct. Running ./gradlw test works. From IDEA, running all the tests from the test root works.

In IDEA running a specific feature or feature file (right-click run). Does not work (it's always null by the time you get to the loader). Also, right-clicking on a scenario to run it yields 0 Scenarios.

It doesn't seem like junit @RunWith stub is being used when IDEA runs an individual feature. (all my stubs have an ObjectFacory, and there are 3 of them total, so I always get the error about dupe factories.).

If I look at the cmd-line IDEA is passing to cucumber, the --object-factory param is not set.

So this is a bug in the IDEA cucumber plugin?

I think this hasn't come up because if you are using a loaded service, and it's the only one, that becomes the default. Which is likely what most want.

How would the plugin fix this? Given a test/feature it has to examine all the stubs to find the object factory associated with the feature? And if there is more than one, run the cmd-line once for each?

Thanks for the link to #2400

@mpkorstanje
Copy link
Contributor

Check the run config, it allows for extra input. Everything else would be a feature request to IDEA.

Though fundamentally, once you have more then one object factory you have a sufficiently complex usecase that the lack of usability is a problem for you to solve.

Possibilities could be:

  • Use different modules, each with their own cucumber.properties file and object factory settings.
  • Change your setup such that you don't need multiple object factories. You may have an implicit condition that should be made explicit in the scenario you are testing. Once made explicit the need for multiple object factories tends to disappear.

@TwoClocks
Copy link
Author

Thanks for the tips.

I want to run a set of features twice, with a slightly different implementation each time, and I'm using factories to do that. I can't figure out any other way. It doesn't make any sense to make it explicit in the scenarios. It has no meaning at that level. For example in the documentation's "Is it Friday yet?" it would need to specify which time-library the app-under-test should use. The expectation is that the tests and their results won't change. I think it would be odd to put that in the scenario. And what if there is more than one library to test?

Any advice on how to do this?

Using a ObjectFactory is the only way I could find to pass any information between the junit stub (how I get the functions to run twice) and the step-def code. Is there another way?

@mpkorstanje
Copy link
Contributor

If the thing to change is the dependency on the time library implementation then consider manipulating the dependencies used.

Cucumber JVM does this for the CDI implementations.

With different profiles to enable/disable while developing:

https://github.com/cucumber/cucumber-jvm/blob/main/jakarta-cdi/pom.xml#L103

And multiple test executions in CI:

https://github.com/cucumber/cucumber-jvm/blob/main/jakarta-cdi/pom.xml#L245

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants