It’s been a while since I wrote about Cucumber, but hopefully I’m not too rusty. As I mentioned in my last post, I’ve recently gotten back into Cucumber. For my current client, I am developing a framework which allows testing the same behavior on multiple applications. To me, this is reminiscent of my first exposure to Cucumber, using it to run through similar behaviors on the web, services, and a database. I previously espoused on multiple best practices of using Cucumber, including tagging, background steps and hooks, and general best practices. In this post, I plan to expand on some of the best practices of glue code.

Organize Your Test Steps

Keep it DRY, “Don’t Repeat Yourself.” You’ll hear this a lot when writing code, and guess what, your glue code is code! So, what does this mean for our Cucumber tests? Ensuring you have one test step for each action that you are trying to accomplish, and that the information contained within, has the appropriate flexibility.

One personal pet peeve of mine is code that is poorly organized. While I’m just as guilty of this as any other developer, knowing where to find a certain statement logically in a growing codebase is important. Following this, I like to organize my glue code based on functionality, with steps in the appropriate, separate classes. If I’m writing WebDriver tests, I like my glue code to follow the same breakup/structure as my POM (Page Object Model for those uninitiated).

Well, one drawback to Cucumber is when you break up your test steps, you often need a way to pass variables and data to these separated classes. Cucumber Step classes don’t have, nor support a traditional constructor, allowing the passing in of variables. It’s important to ensure the same objects are being used across multiple Step classes, after all, for a WebDriver test, we don’t want separate Step classes running on separate browsers. This might even be some user you are acting on or some page data you are collecting to be later verified or re-used. In order to split these steps out, but still retain access to the data, this leaves you with two options typically: extending a base method, or using Dependency Injectors.

I’ve run into more than a few occasions when I’ve wanted to create some test steps as interfaces (setup different browsers, set up different workflows, etc), and as hopefully you know, interfaces can’t extend classes. So this leaves us with the need to use Dependency Injectors. Personally, I like using PicoContainer. It’s simple, no annotations are needed, and it gets the job done.

What is a Dependency Injector

At their core, Dependency Injectors (DIs) are about being able to extend an object’s behavior at runtime by injecting business logic. While this allows you to do a lot of awesome things, for this post, we’re going to discuss one particular capability that DIs allow: passing objects from one class to another, instead of hardcoding values or steps. There are a lot of different DI tools out there; this post will focus on PicoContainer, mainly due to its simplicity, and purposeful design for Cucumber. For more on Dependency Injectors, I’d suggest reading through some of Martin Fowler’s posts. PicoContainer’s most important feature is its ability to instantiate arbitrary objects. This is done through its API, which is similar to a hash table. You can put java.lang.Class objects in and get object instances back.

Most of all that is needed to know to use DIs effectively in Cucumber is to set up classes which need objects passed in with these objects declared in their constructor.

    public LoginSteps(Controller controller, User user) {
        this.controller = controller;
        this.user = user;
    }

This code, at its heart, takes two objects, and stores them, for later usage in test steps. In this example, our controller contains our WebDriver object, which has previously been instantiated in a @Before step, also with the same DI setup. This means all our Step classes have access to the same WebDriver object, and can all perform the same actions on our browser. One major thing to note is that both Controller and User don’t have constructors. This is important, as otherwise, PicoContainer would not be able to create an instance of these objects and provide the same instance to each class looking for it.

DI is wonderful for simplifying code and ensures that values don’t need to be hard-coded or accessed in any kludgey fashion. Your code will look cleaner, you’ll find less duplication, and ultimately it should be easier to maintain.

Some Sample Code

Below are some snippets from a potential Maven Java project.

Setting up your pom

Just include Picocontainer

    …
    <dependencies>
        …
        <!-- https://mvnrepository.com/artifact/info.cukes/cucumber-picocontainer -->
        <dependency>
            <groupId>info.cukes</groupId>
            <artifactId>cucumber-picocontainer</artifactId>
            <version>1.2.5</version>
        </dependency>
        …

Setting up a shared WebDriver Instance

Create your WebDriver class

public class Controller {
    private WebDriver driver;

    public WebDriver getDriver() {
        return driver;
    }

    public void setDriver(WebDriver driver) {
        this.driver = driver;
    }

    public void setupController() {
        this.driver = new ChromeDriver();
    }

    public void teardownController() {
        if (driver != null) {
            driver.quit();
        }
    }
}

Instantiate the driver in one Steps class

public class SetupSteps {
    Controller controller;
    User user;

    public Setup(Controller controller, User user) {
        this.controller = controller;
        this.user = user;
    }

    @Before
    public void setup() {
        controller.setupController();
    }

    @After
    public void teardown() {
        controller.teardownController();
    }
}

Finally, let’s reuse the controller in another class

public class LoginSteps {

    Controller controller;
    User user;
    LoginWorkflow loginPage;

    public LoginSteps(Controller controller, User user) {
        this.controller = controller;
        this.user = user;
        loginPage = new LoginPage(this.controller);
    }

    @When("^I login$")
    public void login() {
        this.loginPage.loadEnvironment();
        this.loginPage.login(this.user);
    }
}

No mess. No extends. Just very clean, simple code. Note, nothing needed to be called to get instantiated, between Cucumber and PicoContainer, this is all handled as part of the framework, with no additional code needed.

Using PicoContainer for Interfaces

One of the main points I made above, is that having your test step classes extend a base class doesn’t work if you test step classes are interfaces. So, how would one do that?
First, we need to tell PicoContainer to use a custom factory. To do that, in your src/test/resources directory, create a file called cucumber.properties. The contents of that file should just point to your custom factory, that defines your interface implementation:

cucumber.api.java.ObjectFactory=com.sample.controllers.CustomPicoFactory

Then, define your interface, and some implementations

public interface Controller {
    public Device getDevice();
    public WebDriver getDriver();
    public void setDriver(WebDriver driver);

    public void setupController();
    public default void teardownController() {
        if (getDriver() != null) {
            getDriver().quit();
        }
    }
}

public class ChromeController implements Controller {
    private Device device = Device.CHROME;
    private WebDriver driver;
    @override
    public Device getDevice() {
        return device;
    }
    @override
    public WebDriver getDriver() {
        return driver;
    }
    @override
    public void setDriver(WebDriver driver) {
        this.driver = driver;
    }
    @override
    public void setupController() {
        this.driver = new ChromeDriver();
        setupLogging();
    }
}

public class FirefoxController implements Controller {
    private Device device = Device.FIREFOX;
    private WebDriver driver;
    @override
    public Device getDevice() {
        return device;
    }
    @override
    public WebDriver getDriver() {
        return driver;
    }
    @override
    public void setDriver(WebDriver driver) {
        this.driver = driver;
    }
    @override
    public void setupController() {
        this.driver = new FirefoxDriver();
        setupLogging();
    }
}

The above code, is setting up the a controller with a WebDriver object, based on the device provided. In this case, it’s being provided as a system property – passed into Maven as -Ddevice=chrome. All of this is handled via a custom Device class.

public enum Device {
    FIREFOX, CHROME;

    public static final Logger log = Logger.getLogger(Device.class);

    /**
     * allows the browser selected to be passed in with a case insensitive name
     *
     * @param b - the string name of the browser
     * @return Browser: the enum version of the browser
     * @throws InvalidDeviceException If a browser that is not one specified in the
     *                                Selenium.Browser class is used, this exception will be thrown
     */
    public static Device lookup(String b) throws InvalidDeviceException {
        for (Device browser : Device.values()) {
            if (browser.name().equalsIgnoreCase(b)) {
                return browser;
            }
        }
        throw new InvalidDeviceException("The selected device " + b + " is not an applicable choice");
    }

    public static Device getDevice() {
        Device device = Device.FIREFOX;
        if (System.getProperty("device") != null) {
            try {
                device = Device.lookup(System.getProperty("device"));
            } catch (Exception e) {
                log.warn("Provided device does not match options. Using Firefox instead. " + e);
            }
        }
        return device;
    }
}

Finally, the last step is to set up our factory defined in properties file above. For our example, it would look something like this:

public class CustomPicoFactory extends PicoFactory {
    public CustomPicoFactory() {
        switch (Device.getDevice()) {
            case FIREFOX:
                addClass(FirefoxController.class);
                break;
            case CHROME:
                addClass(ChromeController.class);
                break;
            default: // if no device is specified, use firefox
                addClass(FirefoxController.class);
        }
    }
}

And of course, we might want to add more implementations of our Controller class for a more complex factory above.

Hopefully, this will get you started down the right track, writing good, clean code. Leave comments below, and of course happy testing!

13 thoughts to “Using Dependency Injectors to Simplify Your Code in Cucumber

  • jitendra

    hello,
    I am getting following error while implementing CustomFactory Pico Container.
    org.picocontainer.injectors.AbstractInjector$UnsatisfiableDependenciesException: steps.LoginSteps has unsatisfied dependency ‘interface steps.Controller’ for constructor ‘public steps.LoginSteps(steps.Controller)’ from org.picocontainer.DefaultPicoContainer@1b4f2a9:1<|

    Here is my classes

    public class LoginSteps {
    LoginPage loginPage;

    public LoginSteps(Controller controller){
    this.loginPage = new LoginPage(controller);
    }

    @Given("^I am on login page$")
    public void i_am_on_login_page() throws Throwable {
    System.out.println("I am on login page");
    }

    @When("^I enter usrname and password$")
    public void i_enter_usrname_and_password() throws Throwable {
    System.out.println("I enter username and password");
    }

    @When("^I click on login button$")
    public void i_click_on_login_button() throws Throwable {
    loginPage.loginToPage();
    }

    @Then("^I am on home page$")
    public void i_am_on_home_page() throws Throwable {
    System.out.println("I am on home page.");
    }

    }

    public class LoginPage {
    public Controller controller;

    public LoginPage(Controller controller) {
    this.controller = controller;
    }

    public void loginToPage(){
    System.out.println("Logged in to application");
    }
    }

    public interface Controller {
    public Device getDevice();

    public WebDriver getDriver();

    public void setDriver(WebDriver driver);

    public void setupController();

    public default void teardownController() {
    if (getDriver() != null) {
    getDriver().quit();
    }
    }
    }

    public class CustomPicoFactory extends PicoFactory {

    public CustomPicoFactory() {
    switch (Device.getDevice()) {
    case FIREFOX:
    addClass(FirefoxController.class);
    break;
    case CHROME:
    addClass(ChromeController.class);
    break;
    default:
    addClass(FirefoxController.class);
    }
    }
    }

    Feature: Login

    Scenario: Login with valid data
    Given I am on login page
    When I enter usrname and password
    And I click on login button
    Then I am on home page

    info.cukes
    cucumber-java
    1.2.5
    test

    info.cukes
    cucumber-junit
    1.2.5
    test

    info.cukes
    cucumber-picocontainer
    1.2.5
    test

    org.seleniumhq.selenium
    selenium-java
    3.10.0

    Reply
    • Max Saperstone

      Did you setup your cucumber.properties to point this this picofactory?

      Reply
  • jitendra

    yes

    something like this

    cucumber.api.java.ObjectFactory=steps.CustomPicoFactory

    Could you please share the github repo of this project if possible?

    Reply
    • Max Saperstone

      Unfortunately I can not. I can try to pull up a fresh repo with some steps, but that will take a while. Are you able to share your repository?

      Reply
  • Pingback: Running Cucumber with Maven - Coveros

  • Dang

    Hi Max

    I followed your guide but when i excuted, i got error:
    WARNING: An illegal reflective access operation has occurred
    WARNING: Illegal reflective access by cucumber.deps.com.thoughtworks.xstream.core.util.Fields (file:/C:/Users/ASUS/.m2/repository/io/cucumber/cucumber-jvm-deps/1.0.6/cucumber-jvm-deps-1.0.6.jar) to field java.util.TreeMap.comparator
    WARNING: Please consider reporting this to the maintainers of cucumber.deps.com.thoughtworks.xstream.core.util.Fields
    WARNING: Use –illegal-access=warn to enable warnings of further illegal reflective access operations
    WARNING: All illegal access operations will be denied in a future release

    Could you please help me check. Below as my repository:
    https://github.com/dangnh007/DN_AutomationFramework

    Thanks in advance

    Reply
    • Max Saperstone

      This doesn’t appear to have anything to do with the pico container, it looks like some probably with your overall cucumber setup.

      Reply
  • Dang

    Or could you please share code of LoginWorkflow page?

    Reply
  • Srilatha

    Try below code it should work.
    Remove super(driver) inside constructor instead of that use below code
    /*************************/
    private BasePage basePage;
    public HomePage(){
    basePage = new BasePage(driver);
    }
    /*************************/
    Let me know if you need more info

    Reply
  • Dang

    Hi Max.

    Thanks for your reply.
    I follow your code but i got error when ran automation as below:
    org.picocontainer.injectors.AbstractInjector$UnsatisfiableDependenciesException: stepdefs.LoginSteps has unsatisfied dependency ‘interface controllers.Controller’ for constructor ‘public stepdefs.LoginSteps(controllers.Controller)’ from org.picocontainer.DefaultPicoContainer@ef2ff9c:2<|

    Could you please help me

    Reply
    • Max Saperstone

      Could you post your code? It looks like either there is a problem with your controller interface, or how it’s being implemented, or how it’s being instantiated by picocontainer

      Reply
  • Dang

    Hi Max

    Thanks for your reply. I think there is mistake in my code. Below as my code:
    https://github.com/dangnh007/DN_AutomationFramework
    Could you please help me check it?
    Thank in advance.

    Dang

    Reply
  • Manish

    //Base class
    public class Base {
    public WebDriver driver;
    }

    //Hook
    public class Hook extends Base {

    private Base base;
    Scenario scenario;

    public Hook(Base base) {
    this.base=base;
    }

    @Before()
    public void initializeTest(Scenario scenario) {
    System.setProperty(“webdriver.gecko.driver”,”D:/Softwares/Automation/geckodriver-v0.23.0-win64/geckodriver.exe”);
    base.driver=new FirefoxDriver();
    base.driver.manage().window().maximize();
    base.driver.manage().timeouts().implicitlyWait(5, TimeUnit.SECONDS);
    System.out.println(“Scenario in execution: “+ scenario.getName());
    }

    @After()
    public void tearDown(Scenario scenario) {
    scenario.write(“Execution completed”);
    if(scenario.isFailed()) {
    System.out.println(scenario.getName());
    }
    base.driver.close();
    }
    }

    //LoginDefinition
    public class LoginDefinition extends Base {

    private Base base;

    public LoginDefinition(Base base) {
    this.base=base;
    }

    @Given(“^User is on landing page$”)
    public void openBrowser() {
    base.driver.get(“http://newtours.demoaut.com/”);
    }

    @When(“^User enter valid username (.+) and valid password (.+) and clicks on SingIn button$”)
    public void validuserLogin(String username, String password) {
    LoginPage l1=new LoginPage(base.driver);
    l1.login(username, password);
    l1.clickSignIn();
    }
    }

    I am getting following erro:
    cucumber.runtime.CucumberException: class stepDefinitions.LoginDefinition doesn’t have an empty constructor. If you need DI, put cucumber-picocontainer on the classpath

    Can someone help?

    Reply

Leave a comment

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

X