Arquillian and Cucumber integration is represented by Cukespace project, a very active with nice examples project.
One issue I’ve found with cukespace(also with arquillian jbehave) is the fact that I can’t use arquillian-persistence extension to initialize my scenarios datasets, the main cause is that cucumber test life cycle is different from junit/testng so arquillian isnt aware when a test or cucumber event is occurring, e.g. arquillian org.jboss.arquillian.test.spi.event.suite.* events aren’t triggered during cucumber tests execution.
There is an issue at cukespace project.
As arquillian persistence uses DBUnit behind the scenes i solved that limitation(while the issue isnt solved) using pure DBUnit api, here is some sample code, it can be found here.
CrudBdd.java
@RunWith(ArquillianCucumber.class) @Features("features/search-cars.feature") @Tags("@whitebox") public class CrudBdd { @Inject CarService carService; Car carFound; int numCarsFound; @Deployment(name = "cdi-crud.war") public static Archive<?> createDeployment() { WebArchive war = Deployments.getBaseDeployment(); war.addAsResource("datasets/car.yml", "car.yml").//needed by DBUnitUtils addClass(DBUnitUtils.class); System.out.println(war.toString(true)); return war; } @Before public void initDataset() { DBUnitUtils.createDataset("car.yml"); } @After public void clear() { DBUnitUtils.deleteDataset("car.yml"); } @Given("^search car with model \"([^\"]*)\"$") public void searchCarWithModel(String model) { Car carExample = new Car(); carExample.setModel(model); carFound = carService.findByExample(carExample); assertNotNull(carFound); } @When("^update model to \"([^\"]*)\"$") public void updateModel(String model) { carFound.setModel(model); carService.update(carFound); } @Then("^searching car by model \"([^\"]*)\" must return (\\d+) of records$") public void searchingCarByModel(final String model, final int result) { Car carExample = new Car(); carExample.setModel(model); assertEquals(result, carService.crud().example(carExample).count()); } @When("^search car with price less than (.+)$") public void searchCarWithPrice(final double price) { numCarsFound = carService.crud().initCriteria().le("price", price).count(); } @Then("^must return (\\d+) cars") public void mustReturnCars(final int result) { assertEquals(result, numCarsFound); } }
DBUnitUtils is a simple class that deals with DBUnit api, the idea is to initialize database
on Before and After events which are triggered on each scenario execution, also they are triggered for each ‘example‘ which is very important so we can have a clean database on each execution.
Here is my feature file:
Feature: Search cars @whitebox Scenario Outline: simple search and update Given search car with model "Ferrari" When update model to "Audi" Then searching car by model "<model>" must return <number> of records Examples: | model | number | | Audi | 1 | | outro | 0 | @whitebox Scenario Outline: search car by price When search car with price less than <price> Then must return <number> cars Examples: | price | number | | 1390.2 | 0 | | 1390.3 | 1 | | 10000.0 | 2 | | 13000.0 | 3 | @blackbox Scenario Outline: search car by id When search car by id <id> Then must find car with model "<model>" and price <price> Examples: | id | model | price | | 1 | Ferrari | 2450.8 | | 2 | Mustang | 12999.0 | | 3 | Porche | 1390.3 |
DBUnitUtils.java
public class DBUnitUtils { private static DataSource ds; private static DatabaseConnection databaseConnection; public static void createDataset(String dataset) { if (!dataset.startsWith("/")) { dataset = "/" + dataset; } try { initConn(); DatabaseOperation.CLEAN_INSERT.execute(databaseConnection, new YamlDataSet(DBUnitUtils.class.getResourceAsStream(dataset))); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("could not initialize dataset:" + dataset + " \nmessage: " + e.getMessage()); } finally { closeConn(); } } public static void deleteDataset(String dataset) { if (!dataset.startsWith("/")) { dataset = "/" + dataset; } try { initConn(); DatabaseOperation.DELETE_ALL.execute(databaseConnection, new YamlDataSet(DBUnitUtils.class.getResourceAsStream(dataset))); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("could not delete dataset dataset:" + dataset + " \nmessage: " + e.getMessage()); } finally { closeConn(); } } private static void closeConn() { try { if (databaseConnection != null && !databaseConnection.getConnection().isClosed()) { databaseConnection.getConnection().close(); } } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException("could not close conection \nmessage: " + e.getMessage()); } } private static void initConn() throws SQLException, NamingException, DatabaseUnitException { if (ds == null) { ds = (DataSource) new InitialContext() .lookup("java:jboss/datasources/ExampleDS"); } databaseConnection = new DatabaseConnection(ds.getConnection()); }
and car.yml
car: - id: 1 model: "Ferrari" price: 2450.8 - id: 2 model: "Mustang" price: 12999.0 - id: 3 model: "Porche" price: 1390.3 - id: 4 model: "Porche274" price: 18990.23
DBUnit Rest endpoint
Another limitation of persistence-extension is its integration with functional tests(blackbox/RunAsClient/testable=false), see this issue. Basically arquillian persistence needs server side resources like datasource to work but blackbox tests run outside the container, in a separated JVM.
To overcome that limitation I’ve created a DBUnit rest endpoint and deployed within my test so i can make rest calls to the server and create dataset there where i have all needed resources, here is the DBUnitUtils with rest calls:
public class DBUnitUtils { private static DataSource ds; private static DatabaseConnection databaseConnection; public static void createRemoteDataset(URL context, String dataset) { HttpURLConnection con = null; try { URL obj = new URL(context + "rest/dbunit/create/" + dataset); con = (HttpURLConnection) obj.openConnection(); con.setRequestMethod("GET"); con.setDoOutput(true); int responseCode = con.getResponseCode(); if (responseCode != 200) { throw new RuntimeException("Could not create remote dataset\nstatus:" + responseCode + "\nerror:" + con.getResponseMessage()); } } catch (Exception e) { e.printStackTrace(); } finally { if (con != null) { con.disconnect(); } } } public static void deleteRemoteDataset(URL context, String dataset) { HttpURLConnection con = null; try { URL obj = new URL(context + "rest/dbunit/delete/" + dataset); con = (HttpURLConnection) obj.openConnection(); con.setRequestMethod("GET"); con.setDoOutput(true); int responseCode = con.getResponseCode(); if (responseCode != 200) { throw new RuntimeException("Could not create remote dataset\nstatus:" + responseCode + "\nerror:" + con.getResponseMessage()); } } catch (Exception e) { e.printStackTrace(); } finally { if (con != null) { con.disconnect(); } } } }
and DBUnitRest.java
@Path("/dbunit") public class DBUnitRest { @GET @Path("create/{dataset}") public Response createDataset(@PathParam("dataset") String dataset) { try { DBUnitUtils.createDataset(dataset);//i feel like going in circles } catch (Exception e) { return Response.status(Status.BAD_REQUEST).entity("Could not create dataset.\nmessage:" + e.getMessage() + "\ncause:" + e.getCause()).build(); } return Response.ok("dataset created sucessfully").build(); } @GET @Path("delete/{dataset}") public Response deleteDataset(@PathParam("dataset") String dataset) { try { DBUnitUtils.deleteDataset(dataset);//i feel like going in circles } catch (Exception e) { return Response.status(Status.BAD_REQUEST).entity("Could not delete dataset.\nmessage:" + e.getMessage() + "\ncause:" + e.getCause()).build(); } return Response.ok("dataset deleted sucessfully").build(); } }
and here is my functional test which uses Drone and Graphene:
@RunWith(ArquillianCucumber.class) @Features("features/search-cars.feature") @Tags("@blackbox") public class CrudAt { @Deployment(name = "cdi-crud.war", testable=false) public static Archive<?> createDeployment() { WebArchive war = Deployments.getBaseDeployment(); war.addAsResource("datasets/car.yml","car.yml").//needed by DBUnitUtils addPackage(DBUnitUtils.class.getPackage()).addClass(CrudBean.class).addClass(YamlDataSet.class). addClass(YamlDataSetProducer.class). addClass(Row.class).addClass(Table.class).addClass(DBUnitRest.class); war.merge(ShrinkWrap.create(GenericArchive.class).as(ExplodedImporter.class).importDirectory("src/main/webapp").as(GenericArchive.class), "/", Filters.include(".*\\.(xhtml|html|css|js|png|gif)$")); MavenResolverSystem resolver = Maven.resolver(); war.addAsLibraries(resolver.loadPomFromFile("pom.xml").resolve("org.dbunit:dbunit:2.5.0").withoutTransitivity().asSingleFile()); war.addAsLibraries(resolver.loadPomFromFile("pom.xml").resolve("org.yaml:snakeyaml:1.10").withoutTransitivity().asSingleFile()); System.out.println(war.toString(true)); return war; } @ArquillianResource URL url; @Drone WebDriver webDriver; @Page IndexPage index; @Before public void initDataset() { DBUnitUtils.createRemoteDataset(url,"car.yml"); } @After public void clear(){ DBUnitUtils.deleteRemoteDataset(url,"car.yml"); } @When("^search car by id (\\d+)$") public void searchCarById(int id){ Graphene.goTo(IndexPage.class); index.findById(""+id); } @Then("^must find car with model \"([^\"]*)\" and price (.+)$") public void returnCarsWithModel(String model, final double price){ assertEquals(model,index.getInputModel().getAttribute("value")); assertEquals(price,Double.parseDouble(index.getInputPrice().getAttribute("value")),0); } }
here is cucumber reports:
That’s all folks.
Can you please let me know in what format the cucumber report will be?
LikeLike
Hi, it generates html reports and uses jenkins cucumber report plugin: https://github.com/jenkinsci/cucumber-reports-plugin
LikeLike
ThAnku very much… It’s a great article.. Helped me a lot…
LikeLiked by 1 person
Thank’s good article 😉
But One issue, if you could help me..
Given than i use weblogic as remote container AND the test steps are in another class
When i lauch a WhiteBox test using (testable=true) the report is not generated
BUT when i try it using (testable=false) the report is generated but test fails because of the needs of IOC….
Any idea? :S
LikeLike
Hi @Lucho,
do you have a sample project to share?
are you using remote or managed container? have you tried using another container like jboss/wildfly to see if the issue is with wls?
you can open an issue at cukespace project at github.
glad you liked it!
LikeLike
Good article!
LikeLiked by 1 person
Very nice article, I am not able to execute the test cases with tags value “@whitebox”. I can provide more details if you want .
LikeLike
Hi, please provide the stacktrace. The tests are passing when executing by travisci, see here: https://travis-ci.org/rmpestano/cdi-crud#L913
LikeLike