Monday, January 14, 2013

Enhancing Cucumber-JVM Step Definitions With Spring

Enhancing Cucumber-JVM Step Definitions With Spring

Recently, I have had the opportunity to learn and use Cucumber-JVM on a project. I must applaud Aslak Hellesøy, the author of Cucumber and Cucumber-JVM, because it was straightforward to learn and use. In no time I was writing and running behavioral tests. As the project went on and our complexity increased, I found myself running into situations that the basic documentation didn't cover. After digging into the helpful unit tests written by Aslak, I found a few features that may be helpful to the community at large. The final code for these examples can be downloaded from my git-hub project.

Project Setup


I set my project up with Maven and I chose to run Cucumber-JVM using the JUnit runner.  The Cucumber-JVM wiki entry about running Cucumber with JUnit is enough of an overview to use source to fill in the details if you are not familiar with this way of running Cucumber features. With this setup, you can run the Cucumber features with the "mvn clean test" command from the command line or from your favorite IDE by right clicking on the RunCukesTest JUnit testing class.

The Simple Case


I will start with a very simple feature for a pint glass:


Feature: A beverage that holds 16 oz of beer to make available for drinking
  As a drinker
  I want beer that comes in a pint
  In order to drink from it

  Scenario: A full pint should equal 16 oz
    Given I have a pint
    When I fill the pint full
    Then there should be 16 oz of beer in the pint

  Scenario: When a pint dispenses 1 oz then there should be 1 oz less in the pint
    Given I have a pint with 16 oz of beer
    When I dispense 1 oz
    Then there should be 15 oz of beer in the pint

  Scenario: A pint can never hold negative oz
    Given I have a pint with 5 oz of beer
    When I dispense 10 oz
    Then The actual amount dispensed should be 5 oz
    And there should be 0 oz of beer in the pint

  Scenario: A pint can never hold more than 16 oz
    Given I have a pint with 10 oz of beer
    When I fill it with 10 oz
    Then The actual amount filled should be 6 oz
    And there should be 16 oz of beer in the pint

The beverage interface the pint implements:

package com.bohnenkamptech.blog.cucumber;

/**
 * Interface for a beverage
 */
public interface Beverage {

    /**
     * Dispense an amount of liquid from the beverage
     *
     * @param ounces - number of ounces to dispense
     * @return - the number of ounces dispensed. This should equal ounces passed in except
     *         where the beverage does not hold enough ounces to dispense the full amount.
     *         Then the actual amount dispensed is returned.
     */
    public int dispense(int ounces);

    /**
     * Returns the number of ounces held in the beverage
     *
     * @return ounces the beverage currently holding
     */
    public int liquidCurrentlyHolding();


    /**
     * Fill the beverage with an amount of liquid
     *
     * @param ounces - number of ounces to fill into beverage. If the beverage can not
     *               hold the amount specified, it will only fill to the beverage's
     *               capacity.
     * @return the number of ounces actually filled into the beverage
     */
    public int fill(int ounces);


    /**
     * Fill the beverage to it's capacity
     *
     * @return the number of ounces actually filled into the beverage
     */
    public int fillFull();
}

Next, the pint implementation:

package com.bohnenkamptech.blog.cucumber;

/**
 * Implementation of a <code>Beverage</code> that behaves like a pint of beer
 */
public class Pint implements Beverage {

    public static final int MAX_OUNCES = 16;

    int ounces;

    /**
     * Default constructor
     */
    public Pint() {

    }

    /**
     * Constructor that fills the pint with ounces in
     *
     * @param ounces - ounces to fill the pint with on initialization. If ounces is over
     *               <code>MAX_OUNCES</code> will only result in <code>MAX_OUNCES</code>
     *               filling the <code>Pint</code>
     */
    public Pint(int ounces) {
        if (ounces <= MAX_OUNCES)
            this.ounces = ounces;
        else
            this.ounces = MAX_OUNCES;
    }

    public int dispense(int ounces) {
        int ouncesDispensed = 0;

        if (this.ounces >= ounces) {
            this.ounces = this.ounces - ounces;
            ouncesDispensed = ounces;
        } else {
            ouncesDispensed = this.ounces;
            this.ounces = 0;
        }
        return ouncesDispensed;
    }

    public int liquidCurrentlyHolding() {
        return this.ounces;
    }

    public int fill(int ounces) {
        int ouncesFilled = 0;
        if (MAX_OUNCES - this.ounces >= ounces) {
            this.ounces += ounces;
            ouncesFilled = ounces;
        } else {
            ouncesFilled = fillFull();
        }
        return ouncesFilled;
    }

    public int fillFull() {
        int ouncesFilled = MAX_OUNCES - this.ounces;
        this.ounces = MAX_OUNCES;
        return ouncesFilled;
    }
}

Finally, the step definitions (note that this object is a work in progress and is not the final representation checked into git):

public class PintStepsDef {

    Pint pint;

    int dispensed;
    int filled;

    @Given("^I have a pint$")
    public void I_have_a_pint() throws Throwable {
        pint = new Pint();
    }

    @When("^I fill the pint full$")
    public void I_fill_the_pint_full() throws Throwable {
        pint.fillFull();
    }

    @Then("^there should be (\\d+) oz of beer in the pint$")
    public void there_should_be_oz_of_beer_in_the_pint(int ounces) throws Throwable {
        assertEquals("There was not the expected number of ounces in the pint", ounces,
                pint.liquidCurrentlyHolding());
    }

    @When("^I dispense (\\d+) oz$")
    public void I_dispense_oz(int ounces) throws Throwable {
        this.dispensed = pint.dispense(ounces);
    }

    @Given("^I have a pint with (\\d+) oz of beer$")
    public void I_have_a_pint_with_oz_of_beer(int ounces) throws Throwable {
        pint = new Pint(ounces);
    }

    @Then("^The actual amount dispensed should be (\\d+) oz$")
    public void The_actual_amount_dispensed_should_be_oz(int ounces) throws Throwable {
        assertEquals("The amount dispensed was not the amount expected", ounces,
                this.dispensed);
    }

    @When("^I fill it with (\\d+) oz$")
    public void I_fill_it_with_oz(int ounces) throws Throwable {
        this.filled = pint.fill(ounces);
    }

    @Then("^The actual amount filled should be (\\d+) oz$")
    public void The_actual_amount_filled_should_be_oz(int ounces) throws Throwable {
        assertEquals("The amount filled was not the amount expected", ounces,
                this.filled);
    }
}

Running a mvn test at this point should result in the Cucumber Feature running and passing.

Throw In the Drinker

That was pretty straight forward, but lets say I want to introduce a drinker that drinks from the pint:

Feature: A simulated bar patron
  As a drinker
  I want to drink a beverage
  In order to relax and have a good time

  Scenario: A sip should only be 1 oz
    Given I have a full pint
    When I take a sip
    Then there should be 15 oz of beer in the pint

  Scenario: A drink should be 2 oz
    Given I have a full pint
    When I take a drink
    Then there should be 14 oz of beer in the pint

  Scenario: A slam should finish off the beverage
    Given I have a pint with 10 oz of beer
    When I slam it
    Then there should be 0 oz of beer in the pint

The first thing to notice is when you run the above feature to get the step definition snippets is that not all the steps for my drinker are given:

Running RunCukesTest

You can implement missing steps with the snippets below:

@Given("^I have a full pint$")
public void I_have_a_full_pint() throws Throwable {
  // Express the Regexp above with the code you wish you ha
  throw new PendingException();
}

@When("^I take a sip$")
public void I_take_a_sip() throws Throwable {
  // Express the Regexp above with the code you wish you ha
  throw new PendingException();
}

@When("^I take a drink$")
public void I_take_a_drink() throws Throwable {
  // Express the Regexp above with the code you wish you had
  throw new PendingException();
}

@When("^I slam it$")
public void I_slam_it() throws Throwable {
  // Express the Regexp above with the code you wish you ha
  throw new PendingException();
}

Tests run: 33, Failures: 0, Errors: 0, Skipped: 10, Time elapsed: 1.095 sec


You might have been expecting to see step snippets for the "Given" and "Then" steps, but Cucumber sees them as duplicates of the step definitions already defined for Pint because the regular expressions for the steps are the same. In fact, if you were to implement them, it would be the same code in two different places so it is a good thing Cucumber doesn't let this happen.

How do we prevent code duplication, permit reuse and get the step definitions to interact with each other without breaking the inversion of control principal? Here is where Spring dependency injection can help out.

Introducing Spring


Cucumber-JVM comes with built-in support for dependency injection containers like Spring. To turn on this behavior, first you need to add a dependency for the cucumber-spring artifact and then provide the context for Cumber-JVM with a file called cucumber.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" 
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
       xmlns:context="http://www.springframework.org/schema/context" 
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> 

    <context:annotation-config/> 

    <context:component-scan base-package="com.bohnenkamptech"/> 

    <import resource="classpath*:/applicationContext.xml"/> 
</beans>

This is the default Spring application context that Cucumber-JVM is looking for. I added an import for my application's specific context which defines my Pint class as a component:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <!-- this will enable us to autowire the step defs -->
    <context:component-scan base-package="com.bohnenkamptech"/>

    <!-- Register the pint as a component for this application -->
    <bean class="com.bohnenkamptech.blog.cucumber.Pint"/>

</beans>

Now to refactor our Pint step definitions to use the defined bean:

public class PintStepsDef {

    @Autowired
    Pint pint;

    int dispensed;
    int filled;

    @Given("^I have a pint$")
    public void I_have_a_pint() throws Throwable {
        //no op, Spring autowired it
    }

    @Given("^I have a full pint$")
    public void I_have_a_full_pint() throws Throwable {
        pint.fillFull();
    }

    @When("^I fill the pint full$")
    public void I_fill_the_pint_full() throws Throwable {
        pint.fillFull();
    }

    @Then("^there should be (\\d+) oz of beer in the pint$")
    public void there_should_be_oz_of_beer_in_the_pint(int ounces) throws Throwable {
        assertEquals("There was not the expected number of ounces in the pint", ounces,
                pint.liquidCurrentlyHolding());
    }

    @When("^I dispense (\\d+) oz$")
    public void I_dispense_oz(int ounces) throws Throwable {
        this.dispensed = pint.dispense(ounces);
    }

    @Given("^I have a pint with (\\d+) oz of beer$")
    public void I_have_a_pint_with_oz_of_beer(int ounces) throws Throwable {
        pint.fill(ounces);
    }

    @Then("^The actual amount dispensed should be (\\d+) oz$")
    public void The_actual_amount_dispensed_should_be_oz(int ounces) throws Throwable {
        assertEquals("The amount dispensed was not the amount expected", ounces,
                this.dispensed);
    }

    @When("^I fill it with (\\d+) oz$")
    public void I_fill_it_with_oz(int ounces) throws Throwable {
        this.filled = pint.fill(ounces);
    }

    @Then("^The actual amount filled should be (\\d+) oz$")
    public void The_actual_amount_filled_should_be_oz(int ounces) throws Throwable {
        assertEquals("The amount filled was not the amount expected", ounces,
                this.filled);
    }
}


Running this refactored code should work the same, right? Unfortunately, the tests say otherwise:


Results :

Failed tests:
  Then The actual amount dispensed should be 5 oz   : The amount dispensed was not the amount expected expected:<5> but was:<10>
  Scenario: A pint can never hold negative oz       : The amount dispensed was not the amount expected expected:<5> but was:<10>
  Then The actual amount filled should be 6 oz      : The amount filled was not the amount expected expected:<6> but was:<0>
  Scenario: A pint can never hold more than 16 oz   : The amount filled was not the amount expected expected:<6> but was:<0>

Why would make our amounts be off like that when the business logic didn't change and the tests are doing the same thing? The answer lies in the Spring scope. When we defined our Pint bean, we used Spring's default singleton scope. That means only one instance will be created by the spring container and handed out to the objects who request it. So our Pint bean's state is being saved from scenario to scenario. Before, we were re-initializing the Pint object manually in our step definition class. If you look at the results, this makes sense:
  1. Given I have a pint: pint.liquidCurrentlyHolding() == 0
  2. When I fill the pint full: pint.liquidCurrentlyHolding() == 16
  3. Given I have a pint with 16 oz of beer: pint.liquidCurrentlyHolding() == 16
  4. When I dispense 1 oz: pint.liquidCurrentlyHolding() == 15
    • Up to this point we are good and all the then clauses have passed
  5. Given I have a pint with 5 oz of beer: pint.liquidCurrentlyHolding() == 16
    • The step used the fill() method assuming that the pint was just initialized with 0 oz. However, there was 15 oz in there due to the fact that Pint was not re-initialized
  6. When I dispense 10 oz: pint.liquidCurrentlyHolding() == 6 
    • This explains the first test failure, the then clause was expecting 5 oz to be dispensed but because we have 16 oz instead of 5 oz in the pint ,10 oz were dispensed
  7. Given I have a pint with 10 oz of beer: pint.liquidCurrentlyHolding() == 16
    • The step used the fill() method again which explains the second test failure
So how does one get around this? Cucumber-JVM provides is own custom scope for just such a purpose. You can set this scope with a simple addition to the Pint bean definition application context:

  <!-- Register the pint as a component for this application -->
  <bean class="com.bohnenkamptech.blog.cucumber.Pint" scope="cucumber-glue"/>

Rerun the mvn test goal and you will see all the test passed!

Finally, Incorporate the Drinker


Now lets incorporate the Drinker with these step definitions:

public class DrinkerStepsDef {

    @Autowired
    Beverage beverage;
    Drinker drinker = new Drinker();

    @When("^I take a sip$")
    public void I_take_a_sip() throws Throwable {
        drinker.sip(beverage);
    }

    @When("^I take a drink$")
    public void I_take_a_drink() throws Throwable {
        drinker.drink(beverage);
    }

    @When("^I slam it$")
    public void I_slam_it() throws Throwable {
        drinker.slam(beverage);
    }

}

You can see the step definitions for the drinker autowire in a beverage. There are no direct dependencies on the pint step definitions. All the glue is already there so it is just a matter of implementing the Drinker class and running mvn clean test. Voila! Everything should pass.

Conclusion


This article showed how integration with a dependency injection framework like Spring can enable reuse of Cucumber step definition code. I hope these examples have given you a deeper understanding of Cucumber-JVM and a few more tools to put to use on your next projects.  

6 comments:

  1. awesome article, those code actually explain it all!

    thanks sincerely!

    ReplyDelete
  2. Oops. Google sign-in clears the comment. :-(

    This tutorial is awesome, thank you.

    My team is experimenting with declarative cucumber features and imperative, pluggable test runners. Step definitions are thin glue to map English to a test runner. The test runner interface is declarative (e.g. "Given I have a key"), and the test runner implementations are imperative (e.g. "Load a key from a file"). Different test runners can test very different solutions (e.g. library, web service, command-line application) and even different approaches (e.g. two different libraries with different APIs).

    Your article has provided one of two missing pieces for me. Now I can use Spring to inject the test runners into the cucumber step definitions. Each implementation can provide its own cucumber.xml (or some property for same).

    The last remaining challenge is to see whether I can bundle up the cucumber features, and step definitions and test runner interfaces into a jar that each implementation includes as a test dependency.

    Thank you!

    ReplyDelete
    Replies
    1. Thanks for the feedback! For your bundling problem, you may just need the Maven Assembly Plugin (http://maven.apache.org/plugins/maven-assembly-plugin/).

      Delete
  3. ran your project and got below error. autowiring dint work.
    Can you please help?

    java.lang.NullPointerException
    at com.sun.proxy.$Proxy16.dispense(Unknown Source)
    at com.bohnenkamptech.blog.cucumber.Drinker.sip(Drinker.java:13)
    at com.bohnenkamptech.blog.cucumber.DrinkerStepsDef.I_take_a_sip(DrinkerStepsDef.java:22)
    at ✽.When I take a sip(features\drinker.feature:8)

    ReplyDelete
  4. Great !!! I loook for this very long !!!

    ReplyDelete
  5. The second directive has to be the focus on enough clean fresh water being made available for every American and eliminate the prospect of another drought that caused irrevocable damage to this past summers harvested crops.guarantor loans

    ReplyDelete