How to Cucumber Java : ReUse Same Scenario for Core, Service and Web UI Tests

cucumber-logoAn example of unified code to reuse the same functional test scenario. Over unit tests of the core component code, or exposed API and services, and finally the web application UI.

It’s useful for quality: non-regression test, continous integration, fast feedback, short loop, cost reduction, etc.

Step 1 : Write a Generic Cucumber Scenario

person-repository.feature

Feature: Person Repository

  Scenario: Person Creation
    Given an empty repository
    When I create a new Person named 'George' with the system
    Then I should have Person named 'George' in the repository

And give it to developers.

ScreenShot021

Step 2 : Code a Unit Test for Core Component

RunBDDTest.java

package com.damienfremont.blog;

import org.junit.runner.RunWith;

import cucumber.api.CucumberOptions;
import cucumber.api.junit.Cucumber;

@RunWith(Cucumber.class)
@CucumberOptions(strict = true, features = "../features")
public class RunBDDTest {

}

StepDefinitions.java

package com.damienfremont.blog;

import static org.assertj.core.api.StrictAssertions.assertThat;
import cucumber.api.java8.En;

public class StepDefinitions implements En {

	static PersonRepository SYSTEM = new PersonRepository();

	public StepDefinitions() {

		Given("an empty repository", 
			() -> 
				assertThat( SYSTEM.count()).isEqualTo( 0 ));

		When("I create a new Person named '(.*)' with the system", 
			(String name) -> 
				SYSTEM.create( new PersonModel(name)));

		Then("I should have Person named '(.*)' in the repository", 
			(String name) -> 
				assertThat(SYSTEM.read(name)).isNotNull());
		
	}
}

Step 3 : Code API Test with REST client

RunBDDTest.java
(the same as before)

EmbeddedServer.java
(see previous post)

StepDefinitions.java

package com.damienfremont.blog;

import static com.jayway.restassured.RestAssured.expect;
import static org.hamcrest.Matchers.hasItems;
import static org.hamcrest.Matchers.hasSize;
import com.google.common.base.Throwables;
import cucumber.api.java8.En;

public class StepDefinitions implements En {

    private static EmbeddedServer SYSTEM;
    
	public StepDefinitions() {

		Given("an empty repository", 
			() -> 
				expect()
					.statusCode(200)
					.body("results", hasSize(0))
				.when()
					.get("/server/api/person"));

		When("I create a new Person named '(.*)' with the system", 
			(String name) -> 
				expect()
					.statusCode(200)
				.given()
					.contentType("text/plain")
					.content(name)
				.when()
					.post("/server/api/person"));

		Then("I should have Person named '(.*)' in the repository", 
			(String name) -> 
				expect()
					.statusCode(200)
					.body("name", hasItems(name))
				.when()
					.get("/server/api/person"));
		
		Before(
			()-> {
				try {
					SYSTEM = new EmbeddedServer(8080, "/server");
					SYSTEM.start();
				} catch (Exception e) {
					throw Throwables.propagate(e);
				}
		});
		
		After(
			()-> {
				SYSTEM.stop();
			});
	}
}

Step 4 : Code a Web UI Test with Selenium

RunBDDTest.java
(the same as before)

StepDefinitions.java

package com.damienfremont.blog;

import static java.util.concurrent.TimeUnit.SECONDS;
import static org.junit.Assert.assertTrue;
import static org.openqa.selenium.phantomjs.PhantomJSDriverService.PHANTOMJS_EXECUTABLE_PATH_PROPERTY;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.phantomjs.PhantomJSDriver;
import org.openqa.selenium.remote.DesiredCapabilities;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableMap;

import cucumber.api.java8.En;

public class StepDefinitions implements En {

	private static EmbeddedServer server;
	private static WebDriver driver;
	
	public StepDefinitions() {

		Given("an empty repository", 
			() -> {
				driver.get("http://localhost:8080/server");
				assertTrue(driver.findElement(By.cssSelector(
					"#message"))
						.getText().contains("0 items"));
				assertTrue(driver.findElement(By.cssSelector(
					"#items"))
						.getText().isEmpty());
			});

		When("I create a new Person named '(.*)' with the system", 
			(String name) ->{
				driver.get("http://localhost:8080/server");
				driver.findElement(By.cssSelector(
					"#newItem"))
						.sendKeys(name);				
				driver.findElement(By.cssSelector(
					"#newButton"))
						.click();
			});

		Then("I should have Person named '(.*)' in the repository", 
			(String name) ->{
				driver.get("http://localhost:8080/server");
				assertTrue(driver.findElement(By.cssSelector(
					"#message"))
						.getText().contains("1 items"));
				assertTrue(driver.findElement(By.cssSelector(
					"#items"))
						.getText().contains(name));
			});
		
		Before(
			()-> {
				// INIT WEB SERVER (TOMCAT)
				try {
					server = new EmbeddedServer(8080, "/server");
					server.start();
				} catch (Exception e) {
					throw Throwables.propagate(e);
				}
				// INIT WEB BROWSER (SELENIUM + PHANTOMJS)
				driver = new PhantomJSDriver(
				new DesiredCapabilities(ImmutableMap.of( //
						PHANTOMJS_EXECUTABLE_PATH_PROPERTY, //
						new PhantomJsDownloader().downloadAndExtract()
								.getAbsolutePath())));
				driver.manage().timeouts().implicitlyWait(5, SECONDS);
		});
		
		After(
			()-> {
				server.stop();
			});
	}
}

Demo

Test Core Layer

Testing that core component:

class PersonRepository {
	PersonModel create(PersonModel entity) ...
	List<PersonModel> readAll() ...
}

with this project:
ScreenShot002

And launching RunBDDTest.java with JUnit:

0.026 sec (test) + 0.2 sec (startup)

ScreenShot003

Test Service Layer

Testing that service API:

ScreenShot007

with this project:

ScreenShot004

And launching RunBDDTest.java with JUnit:

0.210 sec (test) + 3 sec (startup)

ScreenShot009

Test WebApp Layer

Testing that web UI:

ScreenShot008

with this project:

ScreenShot005

And launching RunBDDTest.java with JUnit:

0.628 sec (test) + 7 sec (startup)

ScreenShot010

Project Layout Example

A common feature file and maven sub-projects

ScreenShot001
Maven 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>com.damienfremont.blog</groupId>
	<artifactId>20150722-test-cucumber_scenario_reuse</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>pom</packaging>
	<modules>
		<module>project-core</module>
		<module>project-service</module>
		<module>project-webapp</module>
	</modules>
	<dependencies>
		<dependency>
			<groupId>com.google.guava</groupId>
			<artifactId>guava</artifactId>
			<version>18.0</version>
		</dependency>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.12</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.assertj</groupId>
			<artifactId>assertj-core</artifactId>
			<version>3.1.0</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>info.cukes</groupId>
			<artifactId>cucumber-junit</artifactId>
			<version>1.2.3</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>info.cukes</groupId>
			<artifactId>cucumber-java8</artifactId>
			<version>1.2.3</version>
			<scope>test</scope>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<source>1.8</source>
					<target>1.8</target>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

And launching all tests with Maven:

17 sec

ScreenShot013

Source

https://github.com/DamienFremont/blog/tree/master/20150722-test-cucumber_scenario_reuse

References

http://jlunaquiroga.blogspot.fr/2014/01/restful-web-services-with-jetty-and.html

http://zsoltfabok.com/blog/2012/08/embedded-web-services-for-testing/

https://github.com/tastejs/todomvc/tree/gh-pages/examples/vanillajs

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s