Cucumber-js: How can I specify step definition file for each feature file?

Created on 2 Feb 2017  ·  18Comments  ·  Source: cucumber/cucumber-js

My Goal

I am trying to create a scalable structure of features and step definitions for a large application and my first shot was trying to link step_definition files to features so that I could use the same step pattern for different step definitions.

My Code

I show my current example:

My folder structure:

/features/sample.feature
/features/example.feature
/features/step_definitions/sample_steps.js
/features/step_definitions/example_steps.js
/features/step_definitions/common/common_steps.js

In my sample.feature I have:

  Scenario: Launching Cucumber
  Given I have some step definitions
   When I check some step definition with parameter "any"
   Then I should see all green "sample"

In my example.feature I have:

  Scenario: Launching Cucumber
  Given I have some step definitions
   When I check some step definition with parameter "any"
   Then I should see all green "example"

The Given and When steps are defined at /common/common_steps.js file and works fine.

The Then step is defined both to sample_steps.js and example_steps.js but differently.

In my sample_steps.js I have:

Then('I should see all green {stringInDoubleQuotes}', (arg) => {
   if (arg !== 'sample') {
     throw 'I should see all green when the argument is "sample"';
   }
   return;
 });

And, finally, in my example_steps.js I have:

Then('I should see all green {stringInDoubleQuotes}', (arg) => {
   if (arg !== 'example') {
     throw 'I should see all green when the argument is "example"';
   }
   return;
 });

The Error

My main goal is to have all green here, but of course, it doesn't work and I get this obviouly error:

Multiple step definitions match:
   I should see all green {stringInDoubleQuotes} - features\step_definitions\example_steps.js:6
   I should see all green {stringInDoubleQuotes} - features\step_definitions\sample_steps.js:6

Cucumber-JVM

I know that in cucumber-jvm we can specify a glue attribute that links features and step_definitions and it's exactly what I'm looking for, but in cucumber-js. Example in Java:

@RunWith(Cucumber.class)
@Cucumber.Options( glue = { "com.app.stepdefinitions.common", "com.app.stepdefinitions.sample" } )
public class SampleFeature{
}

@RunWith(Cucumber.class)
@Cucumber.Options( glue = { "com.app.stepdefinitions.common", "com.app.stepdefinitions.example" } )
public class ExampleFeature{
}

Finally

How can I achieve the same as cucumbr-jvm using cucumber-js?

Most helpful comment

IMO scoping is definitely needed. As your application grows, the number of features expands and you will end up with conflicting descriptions in different contexts.

In my company, our product has hundreds of features and QA has test cases in 100k range.
If we use cucumber we will definitely run into this problem.

Yes, I think you should consider context instead of scoping.
You can have most, if not all, of your step definitions in the default context (or no-context), but there should be a way to specify the context without the need of a custom DSL.

It is the BA and QA who should be writing these tests and any custom DSL is bounded to create confusion and resistance.

The Feature/Scenario already provide contexts by definition, that's why the Gherkin syntax has the indentation.

Adding tags and custom DSL is a workaround of implementation limitation (i.e. a hack, IMO) instead of a solution.

Maybe you can consider this while considering https://github.com/cucumber/cucumber-js/issues/745 ?

How about extending Scenario from defineStep and pass {Given, When, Then} into the callback?

i.e.:

import {defineSupportCode} from 'cucumber'

defineSupportCode(({ Scenario, Given, When, Then }) => {
  Given(...)
  When(...)
  Then(...)
  Scenario('<match scenario description>', ({ Given, When, Then}) => {
    Given('<take precedent of non-contexted>', ...)
    ...
  })
})

All 18 comments

I am trying to create a scalable structure of features and step definitions for a large application and my first shot was trying to link step_definition files to features so that I could use the same step pattern for different step definitions.

Very interesting. cucumber-js has nothing built in like the cucumber-java example you gave. The idea I like for this type of thing, is pushing this logic switching into your world setup or instance. Either way you end up with only one step definition. You can have multiple world definitions that you switch between when defining your support code or have a single world instance that exposes different methods based on context. We don't currently expose the file of the current scenario to running steps but you could use tags to determine your context.

We actually have same need for this. We are using nightwatch-cucumber to run selenium tests and our only solution for now is to add a prefix to each step:

Given [comp1] I click on "Open dialog"

vs

Given [comp2] I click on "Open dialog"

This helps us avoid ambiguous step definitions, but leads to really unreadable feature files.
I tried tinkering around with the cucumber.js source, but found no good hints to add support for this feature.

Could this be realized with some hooks or alternative Worlds?

@leipert what user interface are you envisioning? I believe this logic should live in the world or support for multiple world objects. Then the step definitions just deal with parsing the gherkin matches and passing them to the proper world function. We could add a CLI option for selecting which world object to use.

For the moment if you would like to have multiple worlds / step definitions you can achieve this by putting your code in separate folders and only requiring one of them per run (using the --require CLI option).

Well, "the cucumber book" specifically discourage this way of designing steps. Step is shared between scenarios by design, the same sentence should has consistent meaning despite the feature using it. Once you introduce the scope of steps, it is really easy to create language trap.

So far only tags are close to your purpose in cucuber.js. But only hooks can declare they are specific to tags. If you are certain it is right way for your people, you may invent a DSL, perhaps simple as [feature name] in step pattern.

Thanks, @pinxue . Very useful response. However, I can't understand it:

Well, "the cucumber book" specifically discourage this way of designing steps.

A same action phrase could have different meanings in different contexts. But it's ok. It's good to know the options I have, Actually, we're already walking on a internal DSL to achieve it,

Thanks a lot.

Thanks for your answers @pinxue and @robsonrosa.
Here is my reasoning for scoped step definitions:

  1. _Global variables are bad and do not scale_:
    We just have around 15 feature file files with 10 scenarios each, and it is already hard to structure all step definitions and feature files. For big applications (say 100 feature files with 10 scenarios with an average of 5 Steps) having all steps defined globally seems insane, as it is really hard to track which feature file uses which steps.
  2. _Developers have no control over wording in feature files_:
    In our use case our "management" writes feature files, it is hard to sell them: We do awesome BDD, but you have to limit yourself to an awkward custom DSL.
  3. _If cucumber-jvm has the option, why doesn't cucumber-js ?_
    This may be a bad argument 💃

I see the following approaches to solve the problem for us :

  1. Create a custom DSL with prefixes in step definitions
    Cons: Not nice to work with.
    Pros: needs no changes in cucumber.js
  2. Create glue for cucumber.js
    Cons: May go against the idea of "The Cucumber Book".
    Pros: Feature parity with cucumber.js
  3. Change how we run the tests
    Cons: This feature will be available to less people.
    Pros: needs no changes in cucumber.js

That being said, we probably will go with 3., if the maintainers of cucumber.js think scoped step definitions are out of scope (pun intended) for this project.

@robsonrosa I would be interested in your solution, once you have one.

IMO scoping is definitely needed. As your application grows, the number of features expands and you will end up with conflicting descriptions in different contexts.

In my company, our product has hundreds of features and QA has test cases in 100k range.
If we use cucumber we will definitely run into this problem.

Yes, I think you should consider context instead of scoping.
You can have most, if not all, of your step definitions in the default context (or no-context), but there should be a way to specify the context without the need of a custom DSL.

It is the BA and QA who should be writing these tests and any custom DSL is bounded to create confusion and resistance.

The Feature/Scenario already provide contexts by definition, that's why the Gherkin syntax has the indentation.

Adding tags and custom DSL is a workaround of implementation limitation (i.e. a hack, IMO) instead of a solution.

Maybe you can consider this while considering https://github.com/cucumber/cucumber-js/issues/745 ?

How about extending Scenario from defineStep and pass {Given, When, Then} into the callback?

i.e.:

import {defineSupportCode} from 'cucumber'

defineSupportCode(({ Scenario, Given, When, Then }) => {
  Given(...)
  When(...)
  Then(...)
  Scenario('<match scenario description>', ({ Given, When, Then}) => {
    Given('<take precedent of non-contexted>', ...)
    ...
  })
})

I learn BDD from http://fitnesse.org/ so I may look at BDD differently than in the cucumber community.

Paging @richardlawrence

This is an area where Cucumber is particularly opinionated. It's built around the belief that teams should grow a ubiquitous language, where words and phrases mean exactly one thing in the context of an application and are used consistently. Keeping step defs global maintains a positive pressure to avoid ambiguity.

In the rare cases where an application has multiple distinct bounded contexts (in DDD terms), you would simply divide your Cucumber suite along the same lines your application is divided to reflect that boundary, and step defs would be global within each bounded context.

An article about working with Cucumber and creating boundaries. It implements some of the ideas on this page but doesn't present any great solutions as @richardlawrence has mentioned that you can't configure Cucumber to focus on one a particular class for step definitions. http://confessionsofanagilecoach.blogspot.com/2017/05/teaching-cucumbers-about-boundaries.html

As @leipert said, global variables are bad. I think those of us in the CucumberJVM world are only getting half the story. Cucumber (non JVM) uses a tidy World concept which is a global memory space for the duration of the Scenario. After the Scenario is executed, it is destroyed. This feels like a good solution. But... I've not seeing a good implementation of this pattern in CucumberJVM. So if Cucumber forces us to have a flat/global namespace for step definitions, and CucumberJVM had a clear design pattern for implementing the World pattern, then I'm a little happier. Up to this point, CucumberJVM + World pattern == overly complicated solutions. So far, everything I've seen is more complicated than simply letting me control which step functions go with which feature file. So far the alternatives I've seen give me nothing more valuable for all the effort/complexity. Other types of Cucumber have better World solutions.

Ultimately, even with the World pattern I'm still be unhappy because I know there will be information loss when going from feature file to steps. If I spend great effort: putting my feature files in a good organization, creating good feature file names, declaring my feature in a smart way, naming my scenarios in a smart way--all context is tossed out the window and I'm forced to work with global step definition namespace.

I can only think of keeping the relationship out of the validations of the system and simply add wildcards on the paths for the tests, and make them work, even if it takes modifying some open source framework. might give it a shot as our project grows.

I understand your goal, but I would not recommend it.
I would also recommend having explicit .feature files, with either a background statement or an additional Given step that makes this explicit.

Alternatively you could make two different configuration files.
One for the sample and another for the example.
Then you could point to different step folders.

@robsonrosa @leipert I share your opinion
Almost two years after original post...
Did you get any progress with that? Any workaround?

@cristianmercado19 Sorry, no. I am not using cucumber js anymore.

Ups... I like the way to write feature file completely isolated from the step's implementation.
I have tried to achieve the same goal with _mocha_ but I am not happy with.
If you got any other alternative which commits my goal, more than happy to give a try.
Appreciate your help @leipert .

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

Related issues

hdorgeval picture hdorgeval  ·  3Comments

lamartire picture lamartire  ·  6Comments

igniteram picture igniteram  ·  7Comments

travi picture travi  ·  5Comments

protoman92 picture protoman92  ·  3Comments