Skip to content

Consider cucumber-java-lambda as a replacement for cucumber-java8 #2279

Closed as not planned
@mpkorstanje

Description

@mpkorstanje

cucumber-java8 allows step definitions to be defined as lambda's. This is really nice because it removes the need to type your step definition twice as you would with cucumber-java. So there is a good reason to use lambda's to define step definitions.

Compare:

Given("A gherkin and a zukini", () -> { 

});

@Given("A gherkin and a zukini")
public void a_gherkin_and_zukini(){

}

Unfortunately with cucumber-java8 lambda's must be defined in the constructor of a step definition class. As a result we can not know which step definitions are defined until a Cucumber scenario has started and all world objects are instantiated. This makes it impossible to discover, cache and validate step definitions up front, preventing us from making Cucumber more efficient (#2035).

public class StepDefinitions {
     public StepDefinitions(){
        Given("A gherkin and a zukini", () -> { 

       });
     }
}

Additionally Cucumber uses typetools to determine the type of lambda parameters. This requires the use of of Unsafe to fish in the constant pool. This is a non-trivial process and Cucumber currently uses typetools to facilitate this. However because this fundamentally depends on unsafe operation it is not guaranteed to work in the long run.

Requested solution

  1. Implement cucumber-lambda as an alternative for cucumber-java8 that uses a DSL to build step definitions. Because this DSL is created in a static field it can be discovered in the same way cuucmber-java discovers step definitions and avoids the issues of cucumber-java8.
public class CucumberLambdaStepDefinitions {

    @Glue
    public static CucumberLambda glue = CucumberLambda
        .using(World.class)
        .step("A gherkin and a zukini", (World world) -> () -> { 
             world.setGherkins(1);
             world.setZukinis(1);
        })
        .step("{int} gherkin(s) and {int} zukini(s)", (World world) -> (int gherkins, int zukinis) -> {
             world.setGherkins(gherkins);
             world.setZukinis(zukinis);
        });

       // repeat for hooks, parameter types and data table types, ect
       // hooks should be named beforeAll, beforeEach, beforeStep.
  1. Avoid the use of typetools where possible by specifying all parameter types

  2. The World object is created using DI as usual. Consider the possibility of defining steps/hooks using multiple objects.

CucumberLambda
    .using(GherkinPatch.class, ZukiniPatch.class)
    .step("A gherkin and a zukini", (gherkinPatch, zukiniPatch) -> () -> { 
        // tend to the vegetable garden here
    });

Out of scope

  1. Generate localized vairations of the DSL that use Given/When/Then.
    @Glue
    public static CucumberLambda glue = io.cucumber.lambda.en.CucumberLambda
        .using(World.class)
        .given("A gherkin and a zukini", (World world) -> () -> { 
             world.setGherkins(1);
             world.setZukinis(1);
        })
        .when("{int} gherkin(s) and {int} zukini(s)", (World world) -> (int gherkins, int zukinis) -> {
             world.setGherkins(gherkins);
             world.setZukinis(zukinis);
        });
        // ect.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions