Cucumber-jvm: Multiple step definitions with spring context

Created on 9 Jun 2018  路  13Comments  路  Source: cucumber/cucumber-jvm

This check
@Override public boolean addClass(final Class<?> stepClass) { if (!stepClasses.contains(stepClass)) { checkNoComponentAnnotations(stepClass); if (dependsOnSpringContext(stepClass)) { if (stepClassWithSpringContext != null) { throw new CucumberException(String.format("" + "Glue class %1$s and %2$s both attempt to configure the spring context. Please ensure only one " + "glue class configures the spring context", stepClass, stepClassWithSpringContext)); } stepClassWithSpringContext = stepClass; } stepClasses.add(stepClass); } return true; }

in SpringFactory actually prevents usage of multiple step definitions which have to use spring context.

Most helpful comment

I fixed it by doing the following...

A separate cucumber runner class:

@RunWith(Cucumber.class)
@CucumberOptions(
        plugin = { "pretty", "html:build/reports/cucumber" },
        glue = "com.whatever.feature.steps",
        features = "src/acceptance-test/resources/feature/"
)
public class AcceptanceTestsRunner {}

and a context loader class

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class ContextLoader {
    @Before
    public void setUp() {
    }
}

Afterwards you can implement your step definitions and split them by their concerns.
But make sure that ContextLoader is in the same package as the step definitions are, in my case com.whatever.feature.steps

All 13 comments

That is correct. Can you explain why this maybe a problem?

I would like to handle each feature file in its separate step definition. Each of those needs a spring context because i want to test e.g. login and registration of a web application. For this purpose i have a base step definition each step definition is deriving from

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("development-postgres")
public abstract class BaseAcceptanceTest {
    protected UserDetailsHelper userDetailsHelper;
    protected WebClient webDriver;
    protected URL url;
    protected HtmlPage page;
    private WebApplicationContext context;

    public BaseAcceptanceTest(UserDetailsHelper userDetailsHelper, URL url, WebApplicationContext context) {
        this.userDetailsHelper = userDetailsHelper;
        this.url = url;
        this.context = context;
    }

    public void setUp() {
        userDetailsHelper.deleteAll();

        webDriver = MockMvcWebClientBuilder
                .webAppContextSetup(context)
                .build();
    }

    public void tearDown() {
        webDriver.close();
    }
}

With this configuration i get the Glue class %1$s and %2$s both attempt to configure the spring context. Please ensure only one glue class configures the spring context exception.

Can you explain why you want to handle each feature file with it's own stepdefinition?

Because it's good programming style to keep responsibilities separated. Not only sourcecode but also tests should follow SRP.

E.g. a feature file for Login, another feature file for registration, another feature file for doing something meaningful. So we already separate the features into separate feature files, The same should be done with the step definition. Instead of having one huge step definition which handles all different features, it should be separated by it's responsibility.

Ah, thanks for clarifying. Now I understand where you're coming from.
Yes, the step definitions could/should also be separated into separate files. We usually divide them into groups that are meaningful for our domain (i.e. login steps, account steps, whatever). However, the complete set of step definitions makes up your complete library of step definitions. They should not be tied to one particular feature file (i.e. different features might use login), because that would lead to duplication. And you also want to keep your test code DRY.

Yes exactly. And for this reason it should be possible to each step definition with its own spring boot context.
What is the reason to forbid this possibility with the exception at the moment?

@Ben1980 I'm not sure about the reason why it was built this way. But I don't think it needs to be changed.

For example, we are using Spring in one of our projects.
We use the following setup (note: we use Kotlin, syntac is slightly different from Java, but you should get the general idea):

  • A TestConfiguration class, marked:
@Configuration
@ComponentScan(basePackages = ["cucumber.project"])
class TestConfiguration {
// defines a webdriver Bean
}

Note: where "project" is our project ;)

  • This TestConfiguration defines a WebDriver Bean marked:
@Bean(destroyMethod = "quit")
@Scope("cucumber-glue")
fun webDriver(): WebDriver {
}
  • A generic StepDefs file that defines that it runs with the TestConfiguration, and is injected
    with relevant domain objects and "delegated step defs"; i.e. other step definition classes, containing "helper" steps related to a specific part of the domain, i.e. LoginSteps, AccountSteps, etc.
    ```@SpringBootTest(classes = [TestConfiguration::class])
    class StepDefs(
    val page: PageObject, // your page object here
    val domainObject: DomainObject, // your domain object here
    val delegatedSteps: DelegatedSteps, // your delegated steps / helpers here
    // etc
    ) : En {
* Domain objects marked with: 
```@Component
@Scope("cucumber-glue")

These components are found by the component scan

  • "Delegated StepDefs", or "Helpers" grouped by functionality, marked with:
@Component
@Scope("cucumber-glue")

I've written a little bit about this here: https://medium.com/@mlvandijk/managing-state-in-cucumber-jvm-using-spring-a795e9a1dd18

I would like to handle each feature file in its separate step definition. Each of those needs a spring context because i want to test e.g. login and registration of a web application.

A unit test and a step definition file are conceptually not quite the same thing. Any expectations based on their apparent similarity are mistaken.

The execution unit in a unit test consists of a single method in a class. A unit test is run by invoking this method on an object. This object and its dependencies are instantiated by the spring context manager. This couples the test context to the execution unit, the single method.

The execution unit in cucumber is a pickle (a scenario, sort of). A pickle consists of steps which may invoke _multiple_ methods on _multiple_ objects (i.e step definitions). These objects and its dependencies are also all instantiated by the spring context manager. This couples the text context to the execution unit, the pickle.

Because all step definitions may be invoked by the pickle they need to be able to interact with the same context (the same spring application) there wouldn't be much of a point in creating a separate context for each step definition file.

It used to be possible to provide annotate multiple step definitions. But as the spring context manager only accepts a single configuring class the others would be ignored. There was some logic in place to prevent people from shooting themselves in the foot, but this generally lead to more confusion.

E.g. a feature file for Login, another feature file for registration, another feature file for doing something meaningful. So we already separate the features into separate feature files, The same should be done with the step definition. Instead of having one huge step definition which handles all different features, it should be separated by it's responsibility.

You can create multiple step definition files. Just make sure that only of them as the context configuration on it. If you don't have a good candidate you can add a dummy step to make sure the file is still recognized as a step-definition file.

So, as I understand this, if I have a TestLogin.java and TestSecurity.java, each one with context configuration, they cannot be run at the same time, but only separately. If I want to run all the cucumber tests in the projects, where these two are run in a single run, I must delete all the context configuration except one, to prevent the "Please make sure only one glue class..." error, right?

So I have to change the code before the all-through run....

I am facing the same problem where I use in each step definition java file:

@SpringBootTest(webEnvironment=WebEnvironment.MOCK)
@TestPropertySource("classpath:test.properties")

to load properties files into the context.

Or there are some ways to isolate this loading in another class?

You can put each step definition file and feature in a separate package and put a junit or test ng runner in the same package.

By default they'll pick up all the glue and features in their own package. With https://github.com/cucumber/cucumber-jvm/pull/1439 you'll also be able to include shared steps in another package.

src/main/java/
 | - login
 |    |- TestLogin.java
 |    |- login.feature
 |    |- RunCukes.java
 | - security
 |    |- TestSecurity.java
 |    |- security.feature
 |    |- RunCukes.java
 | - common
 |   |- SharedStepDefs.java
 |   |- OtherStepDefs.java

I fixed it by doing the following...

A separate cucumber runner class:

@RunWith(Cucumber.class)
@CucumberOptions(
        plugin = { "pretty", "html:build/reports/cucumber" },
        glue = "com.whatever.feature.steps",
        features = "src/acceptance-test/resources/feature/"
)
public class AcceptanceTestsRunner {}

and a context loader class

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class ContextLoader {
    @Before
    public void setUp() {
    }
}

Afterwards you can implement your step definitions and split them by their concerns.
But make sure that ContextLoader is in the same package as the step definitions are, in my case com.whatever.feature.steps

At the end, with Spring Boot context config and this, I decide to put all step definition in one Java file and all is clear now. If I want to ignore some tests, just change CucumberOptions({features={xxx}) value(you can specify concrete file here). You leave unrelated methods in Java file along, because as long as the needed step defs in your feature are found, your test passes.

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

Was this page helpful?
0 / 5 - 0 ratings