DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
Building Scalable Real-Time Apps with AstraDB and Vaadin
Register Now

Trending

  • RAML vs. OAS: Which Is the Best API Specification for Your Project?
  • Data Freshness: Definition, Alerts To Use, and Other Best Practices
  • Automated Multi-Repo IBM App Connect Enterprise BAR Builds
  • Measuring Service Performance: The Whys and Hows

Trending

  • RAML vs. OAS: Which Is the Best API Specification for Your Project?
  • Data Freshness: Definition, Alerts To Use, and Other Best Practices
  • Automated Multi-Repo IBM App Connect Enterprise BAR Builds
  • Measuring Service Performance: The Whys and Hows
  1. DZone
  2. Software Design and Architecture
  3. Integration
  4. Integrating Cucumber with Play

Integrating Cucumber with Play

Ricky Yim user avatar by
Ricky Yim
·
Oct. 15, 13 · Interview
Like (1)
Save
Tweet
Share
8.95K Views

Join the DZone community and get the full member experience.

Join For Free

The default application generated by Play has examples of unit and integration tests, however it does not demonstrate how to integrate in any Behaviour Driven Development (BDD) frameworks. The value of a BDD framework like Cucumber is extremely high when attempting to achieve strong collaboration between various stakeholders like Developers and Business Analysts. This post will show you how to get up and running with Cucumber and Play.

  1. Assuming you have already setup a vanilla Play application already, the first thing you need to do is add the dependencies for Cucumber. Go to your build.sbt file and add in the dependencies for Cucumber. In this post, we will be using cucumber-jvm, along with the cucumber-junit launcher.

    name := "sample-with-cucumber"
    
    version := "1.0-SNAPSHOT"
    
    libraryDependencies ++= Seq(
      javaJdbc,
      javaEbean,
      cache
      "info.cukes" % "cucumber-java" % "1.1.5" % "test",
      "info.cukes" % "cucumber-junit" % "1.1.5" % "test"
     )     
    
    play.Project.playJavaSettings
    
    
  2. Let's create a directory to contain our feature files. I generally put my features in a features directory in the root of my application.

    $ mkdir features
    
  3. For this directory to be picked up, we need to add it into the classpath when running tests. This is a change we need to make to the build.sbt file.

    name := "sample-with-cucumber"
    
    version := "1.0-SNAPSHOT"
    
    libraryDependencies ++= Seq(
      javaJdbc,
      javaEbean,
      cache
      "info.cukes" % "cucumber-java" % "1.1.5" % "test",
      "info.cukes" % "cucumber-junit" % "1.1.5" % "test"
     )     
    
    play.Project.playJavaSettings
    
    unmanagedResourceDirectories in Test <+= baseDirectory( _ / "features" )
    
  4. To keep everything simple, let's create a very basic feature file. All this will do is go to the landing page and assert the title of the page is equal to the text "Cucumber".

    Create a file features/example.feature containing the following contents.

    Feature: Testing Cucumber Integration
    
      Scenario: Cucumber Integration
        Given I have setup Play
        When I go to the landing page
        Then the title should be "Cucumber"
    
  5. To get this running, we need a class that is annotated with the Cucumber JUnit runner annotation. So create a class called test/RunCucumber.java with the following contents. The "pretty" flag is optional, but I much prefer that output.  

    import cucumber.api.junit.Cucumber;
    import org.junit.runner.RunWith;
    
    @RunWith(Cucumber.class)
    @Cucumber.Options(format = {"pretty"})
    public class RunCucumber {
    }
    
     
  6. Now let's checkpoint what we have done so far. If everything is working, we should be able to run Cucumber and it should complain that we are missing step definitions.

    $ play test
    
     
    ...
    You can implement missing steps with the snippets below:
    
    @Given("^I have setup Play$")
    public void I_have_setup_Play() throws Throwable {
        // Express the Regexp above with the code you wish you had
        throw new PendingException();
    }
    
    @When("^I go to the landing page$")
    public void I_go_to_the_landing_page() throws Throwable {
        // Express the Regexp above with the code you wish you had
        throw new PendingException();
    }
    
    @Then("^the title should be \"([^\"]*)\"$")
    public void the_title_should_be(String arg1) throws Throwable {
        // Express the Regexp above with the code you wish you had
        throw new PendingException();
    }
    
    
  7. To get the scenario to pass, we need to first put a startup hook in place to initialize our server and test browser. Cucumber does provide a @Before hook but this is executed before each and every scenario. We only want to initialize our server and browser once before the very first scenario, so we need to add some state to manage this ourselves.

    Create a test/GlobalHooks.java class and add the following contents.

    import cucumber.api.java.Before;
    import play.test.TestBrowser;
    import play.test.TestServer;
    import static play.test.Helpers.*;
    
    public class GlobalHooks {
        public static int PORT = 3333;
        public static TestBrowser TEST_BROWSER;
        private static TestServer TEST_SERVER;
        private static boolean initialised = false;
    
        @Before
        public void before() {
            if (!initialised) {
                TEST_SERVER = testServer(PORT, fakeApplication(inMemoryDatabase()));
                TEST_BROWSER = testBrowser(HTMLUNIT, PORT);
                start(TEST_SERVER);
                initialised = true;
            }
        }
    }
    
     

    There's also no shutdown hook in Cucumber, so we need to add the clean up of the server and browser into the JVM shutdown hook.

    import cucumber.api.java.Before;
    import play.test.TestBrowser;
    import play.test.TestServer;
    import static play.test.Helpers.*;
    
    public class GlobalHooks {
        public static int PORT = 3333;
        public static TestBrowser TEST_BROWSER;
        private static TestServer TEST_SERVER;
        private static boolean initialised = false;
    
        @Before
        public void before() {
            if (!initialised) {
                TEST_SERVER = testServer(PORT, fakeApplication(inMemoryDatabase()));
                TEST_BROWSER = testBrowser(HTMLUNIT, PORT);
                start(TEST_SERVER);
                initialised = true;
                Runtime.getRuntime().addShutdownHook(new Thread() {
                    @Override
                    public void run() {
                        TEST_BROWSER.quit();
                        TEST_SERVER.stop();
                    }
                });
            }
        }
    }
    
     
  8. Now we should be able to implement our steps. So create a test/Steps.java file. For the moment, we will just statically reference our TEST_BROWSER. There is a better way to inject this dependency in, which I will cover later.

    import cucumber.api.java.en.Given;
    import cucumber.api.java.en.Then;
    import cucumber.api.java.en.When;
    import static org.fest.assertions.Assertions.assertThat;
    
    public class Steps {
        @Given("^I have setup Play$")
        public void I_have_setup_Play() throws Throwable {
        }
    
        @When("^I go to the landing page$")
        public void I_go_to_the_landing_page() throws Throwable {
            GlobalHooks.TEST_BROWSER.goTo("http://localhost:" + GlobalHooks.PORT);
        }
    
        @Then("^the title should be \"([^\"]*)\"$")
        public void the_title_should_be(String title) throws Throwable {
            assertThat(GlobalHooks.TEST_BROWSER.title()).isEqualTo(title);
        }
    }
    
  9. We should now be in a position to rerun this. The steps should be found however the title of the page will not be "Cucumber" resulting in a failure.

    $ play test
    
    ...
    0m[error] Test Scenario: Cucumber Integration failed: expected:<'[Cucumber]'> but was:<'[Welcome to Play]'>
    ...
    
  10. Let's correct the title of the page and rerun the test.

    Navigate to app/views/index.scala.html. Update the title of the page to be "Cucumber"

    @(message: String)
    
    @main("Cucumber") {
        @play20.welcome(message, style = "Java")
    }
    
  11. Rerun the test and it should succeed.

    1 Scenarios (1 passed)
    3 Steps (3 passed)
    0m3.544s
    
  12. The last series of changes will be around integrating with Guice so we can inject our dependencies into the Steps. This will remove the need to directly reference the test browser.

    Go back into the build.sbt file and add the dependencies for guice as well as cucumber-guice, which provides the binding for guice into cucumber.

    name := "sample-with-cucumber"
    
    version := "1.0-SNAPSHOT"
    
    libraryDependencies ++= Seq(
      javaJdbc,
      javaEbean,
      cache,
      "com.google.inject" % "guice" % "3.0" % "test",
      "info.cukes" % "cucumber-guice" % "1.1.5" % "test",
      "info.cukes" % "cucumber-java" % "1.1.5" % "test",
      "info.cukes" % "cucumber-junit" % "1.1.5" % "test"
     )     
    
    play.Project.playJavaSettings
    
    unmanagedResourceDirectories in Test <+= baseDirectory( _ / "features" )
    
     
  13. Define a test/CucumberModule.java class which will setup our bindings.

    import com.google.inject.AbstractModule;
    import com.google.inject.name.Names;
    import play.test.TestBrowser;
    import play.test.TestServer;
    import static play.test.Helpers.*;
    
    public class CucumberModule extends AbstractModule {
        private static int PORT = 3333;
        private TestServer testServer = testServer(PORT, fakeApplication(inMemoryDatabase()));
        private TestBrowser testBrowser = testBrowser(HTMLUNIT, PORT);
    
        @Override
        protected void configure() {
            bind(TestBrowser.class).toInstance(testBrowser);
            bind(TestServer.class).toInstance(testServer);
            bind(Integer.class).annotatedWith(Names.named("PORT")).toInstance(PORT);
        }
    }
    

    Now change test/GlobalHooks.java to be dependency injected.

    import com.google.inject.Inject;
    import cucumber.api.java.Before;
    import play.test.TestBrowser;
    import play.test.TestServer;
    import static play.test.Helpers.*;
    
    public class GlobalHooks {
        @Inject
        private TestBrowser testBrowser;
        @Inject
        private TestServer testServer;
        private static boolean initialised = false;
    
        @Before
        public void before() {
            if (!initialised) {
                start(testServer);
                initialised = true;
                Runtime.getRuntime().addShutdownHook(new Thread() {
                    @Override
                    public void run() {
                        testBrowser.quit();
                        testServer.stop();
                    }
                });
            }
        }
    }
    

    Change test/Steps.java to be dependency injected.

    import com.google.inject.Inject;
    import com.google.inject.name.Named;
    import cucumber.api.java.en.Given;
    import cucumber.api.java.en.Then;
    import cucumber.api.java.en.When;
    import play.test.TestBrowser;
    import static org.fest.assertions.Assertions.assertThat;
    
    public class Steps {
        @Inject
        private TestBrowser testBrowser;
    
        @Inject
        @Named("PORT")
        private Integer port;
    
        @Given("^I have setup Play$")
        public void I_have_setup_Play() throws Throwable {
        }
    
        @When("^I go to the landing page$")
        public void I_go_to_the_landing_page() throws Throwable {
            testBrowser.goTo("http://localhost:" + port);
        }
    
        @Then("^the title should be \"([^\"]*)\"$")
        public void the_title_should_be(String title) throws Throwable {
            assertThat(testBrowser.title()).isEqualTo(title);
        }
    }
    
  14. Lastly add a features.cucumber-guice.properties file to refer to our module. So this file should have the following contents.

    guiceModule=CucumberModule
    
  15. Now if we rerun everything, then the test should still pass which will prove that everything has been setup correctly.

    $ play test
    
    Feature: Testing Cucumber Integration
    
      Scenario: Cucumber Integration        # example.feature:3
        Given I have setup Play             # Steps.I_have_setup_Play()
        When I go to the landing page       # Steps.I_go_to_the_landing_page()
        Then the title should be "Cucumber" # Steps.the_title_should_be(String)
    
    1 Scenarios (1 passed)
    3 Steps (3 passed)
    0m2.092s
    

So this posting has taken you through the steps required to integrate Cucumber and Play. It also has shown you how to integrate Guice into your Cucumber Steps to keep your code cleaner. An example of this sample application can be found here.

Cucumber (software)

Opinions expressed by DZone contributors are their own.

Trending

  • RAML vs. OAS: Which Is the Best API Specification for Your Project?
  • Data Freshness: Definition, Alerts To Use, and Other Best Practices
  • Automated Multi-Repo IBM App Connect Enterprise BAR Builds
  • Measuring Service Performance: The Whys and Hows

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com

Let's be friends: