How to: Integrating Cucumber with Play

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.

  • 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
  • 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
  • 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” )
  • 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”
  • 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 {
}
  • 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();

}
  • 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();
}
});
}
}
}
  • 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);
}
}
  • 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]’>
  • 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”)
}
  • Rerun the test and it should succeed.
1 Scenarios (1 passed)
3 Steps (3 passed)
0m3.544s
  • 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” )
  • 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);
}
}
  • Lastly add a features.cucumber-guice.properties file to refer to our module. So this file should have the following contents.
guiceModule=CucumberModule
  • 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.

Want to know more about how DiUS can help you?

Offices

Melbourne

Level 3, 31 Queen St
Melbourne, Victoria, 3000
Phone: 03 9008 5400

Sydney
Level 2, 50 York St
Sydney, NSW, 2000
Phone: 02 8014 6640

DiUS wishes to acknowledge the Traditional Custodians of the lands on which we work and gather at both our Melbourne and Sydney offices. We pay respect to Elders past, present and emerging and celebrate the diversity of Aboriginal peoples and their ongoing cultures and connections to the lands and waters of Australia.

Relay Newsletter

Sign up to receive the latest news, insights and case studies from DiUS straight into your inbox.

Subscribe

* indicates required

© 2021 DiUS®. All rights reserved.

Privacy  |  Terms