Select Page
Automation Testing

An All Inclusive Guide to Achieve Cucumber Dependency Injection Using Guice

As JVM-Cucumber supports many different dependency injection frameworks, let's explore how good Guice is and find out how to implement it.

An All Inclusive Guide to Achieve Cucumber Dependency Injection Using Guice - Blog

Dependency Injection is a design pattern used to create dependent objects outside a class and provide those objects to a class through different ways by implementing Inversion of Control. Using Dependency Injection, we can move the creation and binding of the dependent objects outside of the class that depends on them. JVM-Cucumber supports many different dependency injection frameworks, and one of them is Guice. As a leading QA company, we are always on the watch for new tools and frameworks to improve our testing process and so we tested out Guice as well. So in this blog, we will be showing you how to perform Cucumber Dependency Injection Using Guice.

Cucumber Dependency Injection Using Guice:

If you’re going to work in an automation framework from scratch or use an existing one, there are few aspects that you should keep in your mind. For example, you have to ensure that the framework is maintainable, easy to understand, helpful in avoiding coding duplicates, and quick to adapt to any changes. Though these are very basic aspects of a framework, it does require you to follow a few design principles and techniques in it. First off, let’s see why sharing the state between steps in Cucumber-JVM is a necessity.

Well, a Gherkin scenario is created by steps and each step depends on previous steps. That is why we must be able to share the state between steps. Since the tests are implemented as regular Java methods in regular Java classes. If steps are global, then every step in the same package or subpackage relative to the runner will be found and executed. This allows us to define one step in one class and another step in another class.

If you’re writing your first test, then there are high chances that you have just a few steps that can easily be fit into one class. But the real problem arises when there are a bunch of scenarios as it gets exponentially harder to maintain. So that is why dividing the steps between many classes is a good idea.

How do you share the state between different classes for Cucumber-JVM?

The recommended solution in Java is to use dependency injection. That is, inject a common object in each class with steps, an object that is recreated every time a new scenario is executed.
Note – Object State sharing is only for steps and not for scenarios.
Let’s take a look at an example scenario and find out how to share the state between multiple step definition files with a common object.

Example Scenario:

* David Orders a mobile phone from Amazon.

* He receives a defective product.

* He returns the product and requests a replacement.

* Amazon replaces the defective product.

Now, let’s split this example into the Gherkin format.

Cucumber-Guice\src\test\resources\Demo.feature
Feature: Replace the product
Scenario: Defective product should be replaced if user requests for replacement.
Given David orders the mobile phone from Amazon
When He returns the product for replacement
Then He will get a new product from Amazon

The example scenario we have seen talks about two different actions,

1. Purchasing a product from Amazon.

2. Returning a product.

So when we divide the implementation of the steps into different classes, the only file that gets affected is the steps definition. This is where Dependency Injection comes into play as we can use it to overcome this obstacle. So let’s see how to get it done using Guice.

The first change here would be to add new dependencies in the Maven POM File.

This is the dependency for Cucumber to use Guice:

<dependency>
<groupId>info.cukes</groupId>  
<artifactId>cucumber-guice</artifactId>   
<version>1.2.5</version>  
<scope>test</scope>
</dependency>

This dependency to use Google Guice:

<dependency>  
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>4.1.0</version>
<scope>test</scope>
</dependency>
Maven POM File:

This is how the Maven POM file will look like:

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"      
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">   
<modelVersion>4.0.0</modelVersion>
    <groupId>org.example</groupId>
<artifactId>Cucumber-Guice</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>     
<java.version>1.8</java.version>      
<junit.version>4.12</junit.version>      
<cucumber.version>1.2.5</cucumber.version>       
<selenium.version>3.7.1</selenium.version>       
<maven.compiler.source>1.6</maven.compiler.source>      
<maven.compiler.target>1.6</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
           
<groupId>com.google.guava</groupId>           
<artifactId>guava</artifactId>
            <version>22.0</version>
        </dependency>

        <dependency>        
<groupId>info.cukes</groupId>
            <artifactId>cucumber-guice</artifactId>           
<version>${cucumber.version}</version>
            <scope>test</scope>
        </dependency>

        <dependency>   
<groupId>com.google.inject</groupId>
            <artifactId>guice</artifactId>           
<version>4.1.0</version>
            <scope>test</scope>
        </dependency>

        <dependency>  
<groupId>info.cukes</groupId>          
<artifactId>cucumber-java</artifactId>
            <version>${cucumber.version}</version>
            <scope>test</scope>
        </dependency>

        <dependency>          
<groupId>info.cukes</groupId>          
<artifactId>cucumber-core</artifactId>         
<version>${cucumber.version}</version>
            <scope>test</scope>
        </dependency>

        <dependency>      
<groupId>info.cukes</groupId>         
<artifactId>cucumber-junit</artifactId>          
<version>${cucumber.version}</version>
            <scope>test</scope>
        </dependency>

        <dependency>          
<groupId>junit</groupId>        
<artifactId>junit</artifactId>          
<version>${junit.version}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>       
<artifactId>selenium-java</artifactId>         
<version>${selenium.version}</version>
            <scope>test</scope>
        </dependency>

        <dependency>           
<groupId>org.seleniumhq.selenium</groupId>          
<artifactId>selenium-chrome-driver</artifactId>          
<version>${selenium.version}</version>
            <scope>test</scope>
        </dependency>
</dependencies>
</project>

The next step would be to create two classes for the steps. Let’s call them CustomerSteps and ProductSteps.

The idea here is that these classes will share state between steps that depend on the result of an earlier step in the scenario. It is known that sharing state can be done in different ways, and we have used a new class that holds the common data here.

Example:
src\test\java\DemoGuice\Steps\DemoCotainer.java
package DemoGuice.Steps;
import DemoGuice.Pages.ProductPage;
import DemoGuice.Pages.CustomerPage;
import cucumber.runtime.java.guice.ScenarioScoped;
@ScenarioScoped
public class DemoCotainer {
    CustomerPage customerPage ;
    ProductPage productPage;
}

In the above code, the democontainer class is annotated with @ScenarioScoped. So Guice will be able to acknowledge it as something that should be created and made available in different classes.

If we want to use this common data in each step definition file, we can add a constructor that takes the democontainer as an argument. This is where the injection occurs and so let’s take a look at an example to understand it better.

Example:
src\test\java\DemoGuice\Steps\ProductSteps.java
public class ProductSteps {
    private DemoCotainer demoCotainer;
    @Inject
    public ProductSteps(DemoCotainer demoCotainer) {
        this.demoCotainer = demoCotainer;
    }

Now we can use the democontainer to access all of the common fields that are needed across the step definition classes. Here, we have annotated the field democontainer with @Inject. It is worth mentioning that you have the choice to annotate a constructor or a field to allow Guice to set the value. This enables the shared democontainer object to be available for all the steps definition classes.

Implementation of ProductSteps class:
src\test\java\DemoGuice\Steps\ProductSteps.java
package DemoGuice.Steps;
import com.google.inject.Inject;
import cucumber.api.java.en.Given;
import cucumber.api.java.en.Then;
import cucumber.api.java.en.When;
public class ProductSteps {
    private DemoCotainer demoCotainer;
    @Inject
    public ProductSteps(DemoCotainer demoCotainer) {
        this.demoCotainer = demoCotainer;
    }
    @Given("^David orders the mobile phone from Amazon$")
    public void davidOrdersTheMobilePhoneFromAmazon() {      
demoCotainer.productPage.orderMobilePhone();
    }
    @When("^He returns the product for replacement$")
    public void heReturnsTheProductForReplacement() {
        demoCotainer.productPage.requestForReturn();
    }
}
Implementation of CustomerSteps class:
src\test\java\DemoGuice\Steps\CustomerSteps.java
package DemoGuice.Steps;
import com.google.inject.Inject;
import cucumber.api.java.en.Given;
import cucumber.api.java.en.Then;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
public class CustomerSteps {
        @Inject
        private DemoCotainer demoCotainer;
        @Inject
        public CustomerSteps(DemoCotainer demoCotainer) {
                this.demoCotainer = demoCotainer;
        }
        @Then("^He will get new product from Amazon$")
        public void heWillGetNewProductFromAmazon() {         
demoCotainer.customerPage.receiveNewProduct();
        }
}

Conclusion:

We hope you had an enjoyable read while also learning how to use Google Guice for performing cucumber dependency injection. Using Dependency Injection to organize our code better and share state between step definitions has been helpful in streamlining our process to provide the best automation testing services to all our clients. Make sure to stay connected with our blog for more informative blogs.

Comments(0)

Submit a Comment

Your email address will not be published. Required fields are marked *

Talk to our Experts

Amazing clients who
trust us


poloatto
ABB
polaris
ooredo
stryker
mobility