cucumber / cucumber-jvm

Cucumber for the JVM
https://cucumber.io
MIT License
2.69k stars 2.02k forks source link

Unable to execute pre-runner actions junit5 cucumber-junit-platform-engine #2901

Closed kakliniew closed 4 days ago

kakliniew commented 4 days ago

👓 What did you see?

Previously, I used junit4 and Serenity runner. I was able to set the cucumber.plugins, cucumber.tags ects flags before runner loaded them based on other flags in the command line (specific case).

After changing to junit5 this is not possible, because the code from Runner class (or atleast it seems like) is not executed. Is it possible to solve this somehow? I tried my class as a plugin, but it doesn't make sense, because the action happens "too late".

BEFORE:

@RunWith(CucumberSerenityRunner.class)
public class CucumberRunner extends CucumberSerenityRunner {

    public CucumberAcceptanceRunner(Class clazz) throws InitializationError, IOException {
        super(clazz);
    }
    static {
        CucumberSerenityRunner.setPlugins();
    }
}

CucumberSerenityRunner was extending net.serenitybdd.cucumber.CucumberWithSerenity;

AFTER:

@Suite
@IncludeEngines("cucumber")
@SelectClasspathResource("features")
public class CucumberAcceptanceRunner extends CucumberSerenityRunner {
}

Right now CucumberSerenityRunner isn't extending anything (tried with plugin, running function in constructor, static block ect). Also this doesnt work correctly for me https://stackoverflow.com/questions/43282798/in-junit-5-how-to-run-code-before-all-tests

✅ What did you expect to see?

The code that sets properties cucumber.plugin, cucumber.tags ect is executed before the runner loads these values.

📦 Which tool/library version are you using?

No response

🔬 How could we reproduce it?

junit5 + cucumber 7.15.0

📚 Any additional context?

No response

mpkorstanje commented 4 days ago

Serenity is a different project and developed at serenity-bdd. Ultimately you'll have to ask your question there.

When using Cucumber, please read the JUnit Platform Engine documentation from top bottom. As well as all the sections of the JUnit 5 User Guide that are not about JUnit Jupiter.

Conceptually you are currently using a few frameworks stacked on top of each other:

Maven Surefire -> JUnit Platform -> Suite Engine -> JUnit Platform -> Cucumber Engine -> Cucumber

Maven uses the JUnit Platform to run the Suite Engine which then reads the @Suite annotated class and uses that configuration to run Cucumber. So you can not programmatically set properties when using the Suite Engine.

Instead you'll want to use this stack:

An executable main class -> JUnit Platform API -> JUnit Platform -> Cucumber Engine -> Cucumber

Using the JUnit Platform Launcher API would look something like this:

import io.cucumber.junit.platform.engine.Constants;
import org.junit.platform.launcher.Launcher;
import org.junit.platform.launcher.LauncherDiscoveryRequest;
import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder;
import org.junit.platform.launcher.core.LauncherFactory;
import org.junit.platform.launcher.listeners.SummaryGeneratingListener;
import org.junit.platform.launcher.listeners.TestExecutionSummary;

import static io.cucumber.junit.platform.engine.Constants.GLUE_PROPERTY_NAME;
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectDirectory;

public class RunCucumberTest {

    public static void main(String[] args) {
        LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request()
               // Add your configuration parameter here.
                .configurationParameter(Constants.PLUGIN_PROPERTY_NAME, "pretty, html:target/cucumber.html, ect, ect")
                .selectors(
                        selectDirectory("path/to/features")
                )
                .build();

        Launcher launcher = LauncherFactory.create();
        SummaryGeneratingListener listener = new SummaryGeneratingListener();
        launcher.registerTestExecutionListeners(listener);
        launcher.execute(request);

        TestExecutionSummary summary = listener.getSummary();
        // Do something with summary

    }

}
kakliniew commented 2 days ago

@mpkorstanje Thanks! I read everything you have mentioned. I tried using the JUnit platform laucher api. It works, but the problem is that then I cannot run it as JUnit configuration in intelijj only as java application (required by the team). What difference in architecture makes it unable to work as with junit4? Could you please suggest sources, if you know, where this is described?

mpkorstanje commented 2 days ago

I suppose you could look at what Serenity was doing for you. See which of those features you can map to Suite @ConfigurationProperties or Surefire/Gradle configuration options, potentially in combination with multiple @Suite classes and Maven/Gradle profiles.

But ultimately you'll have to ask the Serenity people for a good answer.

What difference in architecture makes it unable to work as with junit4? Could you please suggest sources, if you know, where this is described?

They're completely different architectures. I don't think anyone has written a compressive guide. I suppose you could look at the source.

I think this particular difference comes from when properties are read. With JUnit 4 the runner classes start quite late. So there is time to set properties. With JUnit 5, properties are an integral part of the framework. But you should attach a debugger and examine this yourself.