Inheritance in cucumber step definition - Is it possible?

Published On: 2021/03/02

Are you trying to find a way to implement inheritance in cucumber step definitions? Are you getting the exception cucumber.runtime.CucumberException: You’re not allowed to extend classes that define Step Definitions or hooks? You could stop your search for the solution and follow the steps provided in this article to solve the problem.

Why can’t I inherit the step definition class ?

A step definition class is the place where you write the implementation code to test the behaviour of the system in response to an action or change in state. In a complex system, multiple (input) variables affect the end result of a software API. This could lead to the creation of multiple feature files though we have to duplicate the same steps in these feature files.

Let us take an example of a Rating Engine which calculates the rate of shipment for different types of articles.

  1. Feature File - Calculate the rate for fragile goods

        Feature: Calculate the rate for fragile goods
          Scenario Outline: Calculate the rate of international shipment for fragile goods
          Given Shipment is to the international destination < destination >
          And the type of merchandise is < typeOfMerchandise >
          When calculating the rate of the shipment for fragile goods
          Then the calculated rate is < expectedRate >
    
          Examples:
          | destination | typeOfMerchandise | expectedRate |
          | DXB         | Glass             | 30           |
          | DXB         | Ceramic           | 50           |
    
          Scenario Outline: Calculate the rate of local shipment for fragile goods
          Given Shipment is to the local destination < destination >
          And the type of merchandise is < typeOfMerchandise >
          When calculating the rate of the shipment for fragile goods
          Then the calculated rate is < expectedRate >
    
          Examples:
          | destination | typeOfMerchandise | expectedRate |
          | NY          | Glass             | 10           |
          | LA          | Ceramic           | 15           |

  2. Feature File - Calculate the rate for perishable goods

        Feature: Calculate the rate for perishable goods
          Scenario Outline: Calculate the rate for the international shipment of perishable goods
          Given Shipment is to the international destination < destination >
          And the type of merchandise is < typeOfMerchandise >
          When calculating the rate of the shipment for perishable goods
          Then the calculated rate is < expectedRate >
    
          Examples:
          | destination | typeOfMerchandise | expectedRate |
          | DXB         | Seafood           | 70           |
          | DXB         | Chemicals         | 80           |
    
          Scenario Outline: Calculate the rate for the local shipment of perishable goods
          Given Shipment is to the local destination < destination >
          And the type of merchandise is < typeOfMerchandise >
          When calculating the rate of the shipment for perishable goods
          Then the calculated rate is < expectedRate >
    
          Examples:
          | destination | typeOfMerchandise | expectedRate |
          | NY          | Seafood           | 15           |
          | LA          | Chemicals         | 25           |

In the above example, we could see that the steps 1,2, 4 could be considered as common steps and the 3rd step is unique to the feature file. In this situation naturally an automation tester is tempted to move the common steps to a base step definition class, RateCalculatorBase StepDef.class and creates a specialized class for each feature file by extending the base class. Here we could name those classes PerishableGoodsRateCalculatorSetps.class and FragileGoodsRateCalculatorSteps.class

But, this is not allowed in cucumber. Cucumber creates a new instance of all classes defining stepdefs before each scenario. It then invokes stepdef methods on one of those instances whenever it needs to run a step.

Cucumber create instances for the above three classes to execute each scenario. When it tries to execute the step the calculated rate is, it could see the implementation in 3 instances and exit the execution. We can’t blame cucumber as it is expecting only one instance responsible for a step definition.

How could I move the common steps to a common class and use it in actual step definition classes ?

Inheritance is not the soultion. We have to use composition to resolve this problem. We could achieve this with dependency injection. I have used PicoContainer to achieve dependency injection. Let us try to implement the StepDef classes for the above feature files using compostion

Create a class to implement all the common steps in a rate calculator.

public class RateCalculatorCommonSteps {

    @Given("Shipment is to the local destination {}")
    public void localShipment(String destination) {
        // implementation
    }

    @And("the type of merchandise is {}")
    public void merchandiseType(String type) {
        // implementation
    }

    @Then("the calculated rate is {int}") 
    public void assertCalculatedRate( int expectedRate){
        // implementation
    }

}

Create a class to implement the steps specific to the fragile goods rating feature and inject RateCalculatorCommonSteps to use the common steps.

public class FragileGoodsRateCalculatorSteps {
    private final RateCalculatorCommonSteps commonSteps;

    public FragileGoodsRateCalculatorSteps(RateCalculatorCommonSteps commonSteps) {
        this.commonSteps = commonSteps;
    }

    @When("calculating the rate of the shipment for fragile goods")
    public void calculateFragileGoods(String type) {
        // implementation
    }
}

Create a class to implement the steps specific to the perisable goods rating feature and inject RateCalculatorCommonSteps to use the common steps.

public class PerishableGoodsRateCalculatorSetps {    
    private final RateCalculatorCommonSteps commonSteps;
    
    public PerishableGoodsRateCalculatorSetps(RateCalculatorCommonSteps commonSteps) {
        this.commonSteps = commonSteps;
    }

    @When("calculating the rate of the shipment for perishable goods")
    public void calculatePerishableGoods(String type) {
        // implementation
    }
}

Conclusion

In a complex and large projects, where we use cucumber for automation, we have to use common steps in multiple feature files. In this article, I have tried to explain how we could implement the common steps in a base step definition class and use composition to execute these common steps from a specialized setp definition class.

comments powered by Disqus