Oh JBehave, Baby! Behaviour Driven Development using JBehave
May 31st, 2011 by Micha KopsBehaviour Driven Development is the keyword when we’re talking about test scenarios written in an ubiquitous language, strong interaction with stakeholders, product owners or testers and well described, common understandable test scenarios.
The popular JBehave framework is our tool of choice here and allows us to decouple our test stories from the test classes, offers an integration for web tests using Selenium and finally there’s a helpful Maven plugin for JBehave, too.
After a short excursion into the principles of Behaviour Driven Development we’re going to write and implement test stories for simple acceptance tests and web tests using selenium.
Contents
- Prerequisites
- A short introduction into BDD
- Setting up a test project
- The first test story
- Passing parameters from the story to the test
- Using the WebRunner
- Testing a Web Application
- The JBehave Maven Plugin
- Sources Download
- Troubleshooting
- Alternative: Cucumber for Java and Java EE
- Resources
- BDD Articles of mine
- Article Updates
Prerequisites
We don’t need much for the following tutorial .. just be sure to have a valid installation of Java 6 and Apache Maven .. otherwise install both from the following sources..
A short introduction into BDD
Behaviour Driven Development (BDD) is an agile software development technique that enhances the paradigm of Test Driven Development (TDD) and Acceptance Tests and encourages the collaboration between developers, quality assurance, domain experts and stakeholders.
Dan North described BDD in 2009 as (source):
“BDD is a second-generation, outside-in, pull-based, multiple-stakeholder, multiple-scale, high-automation, agile methodology. It describes a cycle of interactions with well-defined outputs, resulting in the delivery of working, tested software that matters.”
Our basic unit to encapsule a set of features is a story. Acceptance criteria are a part of the story and lead to the definition of done.
Following the structure proposed by Dan North in his article “What’s in a story?” a valid template for a user story would be like this:
Title (one line describing the story) Narrative: As a [role] I want [feature] So that [benefit] Acceptance Criteria: (presented as Scenarios) Scenario 1: Title Given [context] And [some more context]... When [event] Then [outcome] And [another outcome]... Scenario 2: ...
The title should describe an activity e.g. “Customer creates an account” and gives us a point what should be done here.
The narrative should describe roles, goals and the motivation or business value. A common notation is “as a I want so that “.
The scenario’s title should describe what is special about the scenario and should be linked to the user story’s title without repeating it.
A single scenario is described by given initial states, events and outcomes – it is common to use the given/when/then notation here.
If you want to get some more details, please take a look at the chapter “Resources” and the BDD resources listed there, especially Dan North’s Blog and Behaviour-Driven.org offer a lot of useful information.
Setting up a test project
First we need a project to put our test scenarios and testable code in .. Maven to the rescue..
- We’re creating a new simple Maven project via IDE+Maven plugin or by console:
mvn archetype:generate
- In the next step we do what we always do: Setting the source/target level to Java 6, the encoding to UTF-8 and adding some dependencies .. for now your pom.xml should look like this one
<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.hascode.tutorial</groupId> <artifactId>jbehave-bdd-tutorial</artifactId> <version>0.0.1-SNAPSHOT</version> <name>hasCode.com jbehave BDD tutorial</name> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <jbehave.core.version>3.3.2</jbehave.core.version> </properties> <dependencies> <dependency> <groupId>org.jbehave</groupId> <artifactId>jbehave-core</artifactId> <version>${jbehave.core.version}</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.6</source> <target>1.6</target> </configuration> </plugin> </plugins> </build> </project>
The first test story
- Now we need some classes for the test, add them in src/main/java and the package com.hascode.tutorial
package com.hascode.tutorial; public class Product { private String name; /** * @return the name */ public String getName() { return name; } /** * @param name * the name to set */ public void setName(String name) { this.name = name; } }
package com.hascode.tutorial; public class User { private String name; /** * @return the name */ public String getName() { return name; } /** * @param name * the name to set */ public void setName(String name) { this.name = name; } }
package com.hascode.tutorial; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class ShoppingCart { private final Map<User, List<Product>> entries = new HashMap<User, List<Product>>(); public void add(final User user, final Product product) { List<Product> purchases; if (entries.containsKey(user)) { purchases = entries.get(user); } else { purchases = new ArrayList<Product>(); } purchases.add(product); entries.put(user, purchases); } public List<Product> getProductsByUser(final User user) { return entries.get(user); } }
- Also we should define a story for the test in a file named add_product_to_shopping_cart.story in src/main/resources/com/hascode/tutorial
Scenario: When a user adds a product to the shopping cart, the product should be included in the user's shopping cart. Given a user Given a shopping cart Given a product When the user adds the product to the shopping cart Then the product must be included in the list of the shoppingcart's entries
- Finally we’re adding the test class and mapping the fields to the grammar used in the story
package com.hascode.tutorial; import java.util.List; import org.jbehave.core.annotations.Given; import org.jbehave.core.annotations.Then; import org.jbehave.core.annotations.When; import org.jbehave.core.configuration.Configuration; import org.jbehave.core.configuration.MostUsefulConfiguration; import org.jbehave.core.io.LoadFromClasspath; import org.jbehave.core.junit.JUnitStory; import org.jbehave.core.reporters.Format; import org.jbehave.core.reporters.StoryReporterBuilder; import org.jbehave.core.steps.CandidateSteps; import org.jbehave.core.steps.InstanceStepsFactory; import org.junit.Assert; import org.junit.Test; public class AddProductToShoppingCart extends JUnitStory { private User user; private ShoppingCart shoppingCart; private Product product; @Given("a user") public void aUser() { user = new User(); } @Given("a shopping cart") public void aShoppingCart() { shoppingCart = new ShoppingCart(); } @Given("a product") public void aProduct() { product = new Product(); product.setName("Coffee"); } @When("the user adds the product to the shopping cart") public void userAddsProductToShoppingCart() { shoppingCart.add(user, product); } @Then("the product must be included in the list of the shoppingcart's entries") public void productMustBeListed() { List<Product> entries = shoppingCart.getProductsByUser(user); Assert.assertTrue(entries.contains(product)); } @Override public Configuration configuration() { return new MostUsefulConfiguration().useStoryLoader(new LoadFromClasspath(getClass().getClassLoader())).useStoryReporterBuilder(new StoryReporterBuilder().withFormats(Format.CONSOLE)); } @Override public List<CandidateSteps> candidateSteps() { return new InstanceStepsFactory(configuration(), this).createCandidateSteps(); } @Override @Test public void run() throws Throwable { super.run(); } }
- Running the unit test produces the following output
Processing system properties {} (BeforeStories) Using 1 threads Running story com/hascode/tutorial/add_product_to_shopping_cart.story (com/hascode/tutorial/add_product_to_shopping_cart.story) Scenario: When a user adds a product to the shopping cart, the product should be included in the user's shopping cart. Given a user Given a shopping cart Given a product When the user adds the product to the shopping cart Then the product must be included in the list of the shoppingcart's entries (AfterStories) Generating reports view to '/var/project/jbehave-tutorial/target/jbehave' using formats '[stats, console, txt]' and view properties '{defaultFormats=stats, decorateNonHtml=true, viewDirectory=view, decorated=ftl/jbehave-report-decorated.ftl, reports=ftl/jbehave-reports-with-totals.ftl, maps=ftl/jbehave-maps.ftl, navigator=ftl/jbehave-navigator.ftl, views=ftl/jbehave-views.ftl, nonDecorated=ftl/jbehave-report-non-decorated.ftl}' Reports view generated with 3 stories (of which 0 pending) containing 1 scenarios (of which 0 failed and 0 pending)
Passing parameters from the story to the test
In the following test scenario we’re going to pass variables from the story to the test class …
- First we’re adding a new class needed for our tests named UserDao in src/main/resources/com/hascode/tutorial
package com.hascode.tutorial; import java.util.HashSet; import java.util.Set; public class UserDao { private final Set<User> users = new HashSet<User>(); public void save(final User user) { users.add(user); } public Set<User> findAll() { return users; } }
- The next we’re creating a new test story in a file named add_user_to_repository.story in src/test/resources/com/hascode/tutorial
Scenario: When users are added to the repository they must be found afterwards in the repository Given a user repository When the user adds 3 users to the user repository Then the user repository must contain 3 users
- Also we’re creating a new test file named AddUserToRepository in src/main/java/com/hascode/tutorial
package com.hascode.tutorial; import java.util.HashSet; import java.util.List; import java.util.Set; import junit.framework.Assert; import org.jbehave.core.annotations.Given; import org.jbehave.core.annotations.Then; import org.jbehave.core.annotations.When; import org.jbehave.core.configuration.Configuration; import org.jbehave.core.configuration.MostUsefulConfiguration; import org.jbehave.core.io.LoadFromClasspath; import org.jbehave.core.junit.JUnitStory; import org.jbehave.core.reporters.Format; import org.jbehave.core.reporters.StoryReporterBuilder; import org.jbehave.core.steps.CandidateSteps; import org.jbehave.core.steps.InstanceStepsFactory; import org.junit.Test; public class AddUserToRepository extends JUnitStory { private final Set<User> users = new HashSet<User>(); private UserDao userDao; @Given("$amount products") public void someProducts(final int amount) { for (int i = 0; i < amount; i++) { final User user = new User(); user.setName("user " + i); users.add(user); } } @Given("a user repository") public void aUserRepository() { userDao = new UserDao(); } @When("When the user adds $amount users to the user repository") public void userAddsUsersToRepository(final int amount) { for (final User user : users) { userDao.save(user); } } @Then("Then the user repository must contain $amount users") public void productMustBeListed(final int amount) { Assert.assertEquals(amount, userDao.findAll().size()); } @Override public Configuration configuration() { return new MostUsefulConfiguration().useStoryLoader(new LoadFromClasspath(getClass().getClassLoader())).useStoryReporterBuilder(new StoryReporterBuilder().withFormats(Format.CONSOLE)); } @Override public List<CandidateSteps> candidateSteps() { return new InstanceStepsFactory(configuration(), this).createCandidateSteps(); } @Override @Test public void run() throws Throwable { super.run(); } }
- The test produces the following output
Processing system properties {} (BeforeStories) Using 1 threads Running story com/hascode/tutorial/add_user_to_repository.story (com/hascode/tutorial/add_user_to_repository.story) Scenario: When users are added to the repository they must be found afterwards in the repository Given a user repository When the user adds 3 users to the user repository Then the user repository must contain 3 users (AfterStories) Generating reports view to '/var/project/jbehave-tutorial/target/jbehave' using formats '[console]' and view properties '{defaultFormats=stats, decorateNonHtml=true, viewDirectory=view, decorated=ftl/jbehave-report-decorated.ftl, reports=ftl/jbehave-reports-with-totals.ftl, maps=ftl/jbehave-maps.ftl, navigator=ftl/jbehave-navigator.ftl, views=ftl/jbehave-views.ftl, nonDecorated=ftl/jbehave-report-non-decorated.ftl}' Reports view generated with 3 stories (of which 0 pending) containing 1 scenarios (of which 0 failed and 0 pending)
Using the WebRunner
The WebRunner offers a nice web interface to control JBehave and modify your test scenarios …
- Add the directory for the web application data
mkdir -p src/main/webapp/WEB-INF
- Add the web.xml to the WEB-INF directory
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> <display-name>hasCode.com - JBehave WebRunner Example</display-name> <servlet> <servlet-name>wicket</servlet-name> <servlet-class>org.apache.wicket.protocol.http.WicketServlet</servlet-class> <init-param> <param-name>applicationClassName</param-name> <param-value>com.hascode.tutorial.runner.TutorialRunnerApplication</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>wicket</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>
- Create a runner where the candidate steps are registered named TutorialRunnerApplication
package com.hascode.tutorial.runner; import java.util.List; import org.jbehave.core.steps.CandidateSteps; import org.jbehave.core.steps.InstanceStepsFactory; import org.jbehave.web.runner.wicket.WebRunnerApplication; import com.hascode.tutorial.AddUserToRepository; public class TutorialRunnerApplication extends WebRunnerApplication { @Override protected List<CandidateSteps> candidateSteps() { return new InstanceStepsFactory(configuration(), AddUserToRepository.class).createCandidateSteps(); } }
- Change the project to create a war archive, add dependencies for the JBehave WebRunner and configure the Jetty Maven Plugin – my pom.xml now looks like this one
<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.hascode.tutorial</groupId> <artifactId>jbehave-bdd-tutorial</artifactId> <version>0.0.1-SNAPSHOT</version> <name>hasCode.com jbehave BDD tutorial</name> <packaging>war</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <jbehave.core.version>3.3.2</jbehave.core.version> <jbehave.webapp.name>jbehave-webapprunner</jbehave.webapp.name> </properties> <dependencies> <dependency> <groupId>org.jbehave</groupId> <artifactId>jbehave-core</artifactId> <version>${jbehave.core.version}</version> </dependency> <dependency> <groupId>org.jbehave.web</groupId> <artifactId>jbehave-web-runner</artifactId> <version>${jbehave.core.version}</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.6</source> <target>1.6</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <executions> <execution> <id>unpack-resources</id> <phase>process-resources</phase> <goals> <goal>unpack</goal> </goals> <configuration> <outputDirectory>${project.build.directory}/resources</outputDirectory> <overWriteReleases>true</overWriteReleases> <overWriteSnapshots>true</overWriteSnapshots> <excludes>**/*.class</excludes> <artifactItems> <artifactItem> <groupId>org.jbehave.web</groupId> <artifactId>jbehave-web-runner</artifactId> <version>${jbehave.core.version}</version> </artifactItem> </artifactItems> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <executions> <execution> <id>copy-custom-resources</id> <phase>process-resources</phase> <goals> <goal>copy-resources</goal> </goals> <configuration> <outputDirectory>${project.build.directory}/resources</outputDirectory> <overwrite>true</overwrite> <resources> <resource> <directory>${basedir}/src/main/webapp</directory> </resource> </resources> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <configuration> <webResources> <resource> <directory>${project.build.directory}/resources</directory> </resource> </webResources> </configuration> </plugin> <plugin> <groupId>org.mortbay.jetty</groupId> <artifactId>jetty-maven-plugin</artifactId> <configuration> <webAppConfig> <contextPath>/${jbehave.webapp.name}</contextPath> </webAppConfig> <webApp>${project.build.directory}/${jbehave.webapp.name}.war</webApp> </configuration> </plugin> </plugins> </build> </project>
- Run the application with an embedded Jetty Webserver
mvn jetty:run
- Now you’re able open the WebRunner application in your browser at the following URL: http://localhost:8080/jbehave-webapprunner/home
- You’re now able to play around .. take a look at the screenshots below
Home Datafiles Find Steps Run Stories
Testing a Web Application
Now we want to test a web site .. let’s say a popular search engine (but please do not spam them! :)
- First we’re adding some dependencies to our pom.xml .. the dependency for selenium server depends on if you want to start an embedded selenium server in your tests or you’re using an existing standalone server
<dependency> <groupId>org.seleniumhq.selenium.client-drivers</groupId> <artifactId>selenium-java-client-driver</artifactId> <version>1.0.2</version> </dependency> <!-- only needed if you don't want to use a standalone selenium server --> <dependency> <groupId>org.openqa.selenium.server</groupId> <artifactId>selenium-server</artifactId> <version>1.0-20081010.060147</version> </dependency>
- Next our test story in a file named search_popular_search_engine.story
Scenario: Search for the hascode.com website on Google Given The Google homepage When I search for "site:hascode.com" Then the text "hasCode.com » 2011 » March" is present When I click the link "hasCode.com" Then the text "Recent Articles" is present And the page's URL should be "https://www.hascode.com/"
- Furthermore we’re defining our steps in the class SearchPopularSearchEngineSteps
package com.hascode.tutorial; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; import static org.junit.matchers.JUnitMatchers.containsString; import org.jbehave.core.annotations.Given; import org.jbehave.core.annotations.Then; import org.jbehave.core.annotations.When; import org.jbehave.web.selenium.SeleniumSteps; import com.thoughtworks.selenium.Selenium; public class SearchPopularSearchEngineSteps extends SeleniumSteps { private final String pageLoadTimeout = "6000"; public SearchPopularSearchEngineSteps(final Selenium selenium) { super(selenium); } @Given("The Google homepage") public void theGoogleHomepage() { selenium.open("/"); } @When("I search for \"$term\"") public void searchForTerm(final String term) throws InterruptedException { selenium.type("q", term); selenium.click("btnG"); Thread.sleep(4000l); // ugly } @Then("the text \"$textFragment\" is present") public void shouldSee(final String textFragment) { assertThat(selenium.getBodyText(), containsString(textFragment)); } @When("I click the link \"$linkText\"") public void followLinkWithText(final String linkText) { selenium.click("link=" + linkText); selenium.waitForPageToLoad(pageLoadTimeout); } @Then("the page's URL should be \"$url\"") public void pageURLShouldBe(final String url) { assertThat(selenium.getLocation(), is(url)); } }
- Finally we’re putting it all together in the class SearchPopularSearchEngine
package com.hascode.tutorial; import java.util.Arrays; import java.util.List; import org.jbehave.core.Embeddable; import org.jbehave.core.configuration.Configuration; import org.jbehave.core.io.CodeLocations; import org.jbehave.core.io.LoadFromClasspath; import org.jbehave.core.io.StoryFinder; import org.jbehave.core.junit.JUnitStories; import org.jbehave.core.reporters.Format; import org.jbehave.core.reporters.StoryReporterBuilder; import org.jbehave.core.steps.CandidateSteps; import org.jbehave.core.steps.InstanceStepsFactory; import org.jbehave.core.steps.SilentStepMonitor; import org.jbehave.web.selenium.SeleniumConfiguration; import org.jbehave.web.selenium.SeleniumContext; import org.jbehave.web.selenium.SeleniumStepMonitor; import org.junit.Before; import org.junit.Test; import com.thoughtworks.selenium.DefaultSelenium; import com.thoughtworks.selenium.Selenium; public class SearchPopularSearchEngine extends JUnitStories { private final Selenium selenium = new DefaultSelenium("127.0.0.1", 4444, "*chrome", "http://www.google.com/"); private final SeleniumContext seleniumContext = new SeleniumContext(); @Override public Configuration configuration() { Class<? extends Embeddable> embeddableClass = this.getClass(); return new SeleniumConfiguration().useSelenium(selenium).useSeleniumContext(seleniumContext).useStepMonitor(new SeleniumStepMonitor(selenium, seleniumContext, new SilentStepMonitor())) .useStoryLoader(new LoadFromClasspath(embeddableClass)).useStoryReporterBuilder(new StoryReporterBuilder().withFormats(Format.CONSOLE)); } @Override public List<CandidateSteps> candidateSteps() { return new InstanceStepsFactory(configuration(), new SearchPopularSearchEngineSteps(selenium)).createCandidateSteps(); } @Before public void setUp() throws Exception { selenium.start(); } @Override @Test public void run() throws Throwable { super.run(); } @Override protected List<String> storyPaths() { return new StoryFinder().findPaths(CodeLocations.codeLocationFromClass(this.getClass()).getFile(), Arrays.asList("**/search_popular_search_engine.story"), null); } }
The console output should display the following information
(BeforeStories) Using 1 threads Running story com/hascode/tutorial/search_popular_search_engine.story (com/hascode/tutorial/search_popular_search_engine.story) Scenario: Search for the hascode.com website on Google Given The Google homepage When I search for "site:hascode.com" Then the text "hasCode.com » 2011 » March" is present When I click the link "hasCode.com" Then the text "Recent Articles" is present And the page's URL should be "https://www.hascode.com/"
The Selenium server logs could look like this one
22:10:49.548 INFO - Command request: getNewBrowserSession[*firefox, http://www.google.com, ] on session null 22:10:49.548 INFO - creating new remote session 22:10:49.766 INFO - Allocated session adbf7bd6cfc040b8ad3aeb634a37172b for http://www.google.com, launching... 22:10:49.872 INFO - Preparing Firefox profile... 22:10:52.518 INFO - Launching Firefox... 22:10:55.172 INFO - Got result: OK,adbf7bd6cfc040b8ad3aeb634a37172b on session adbf7bd6cfc040b8ad3aeb634a37172b 22:10:55.586 INFO - Command request: setContext[<b></b><br/>Given The Google homepage, ] on session adbf7bd6cfc040b8ad3aeb634a37172b 22:10:55.610 INFO - Got result: OK on session adbf7bd6cfc040b8ad3aeb634a37172b 22:10:55.612 INFO - Command request: open[http://www.google.com/, ] on session adbf7bd6cfc040b8ad3aeb634a37172b 22:10:56.245 INFO - Got result: OK on session adbf7bd6cfc040b8ad3aeb634a37172b 22:10:56.250 INFO - Command request: waitForPageToLoad[7000, ] on session adbf7bd6cfc040b8ad3aeb634a37172b 22:10:56.257 INFO - Got result: OK on session adbf7bd6cfc040b8ad3aeb634a37172b 22:10:56.260 INFO - Command request: setContext[<b></b><br/>When I search for "site:hascode.com", ] on session adbf7bd6cfc040b8ad3aeb634a37172b 22:10:56.275 INFO - Got result: OK on session adbf7bd6cfc040b8ad3aeb634a37172b 22:10:56.276 INFO - Command request: type[q, site:hascode.com] on session adbf7bd6cfc040b8ad3aeb634a37172b 22:10:56.295 INFO - Got result: OK on session adbf7bd6cfc040b8ad3aeb634a37172b 22:10:56.297 INFO - Command request: click[btnG, ] on session adbf7bd6cfc040b8ad3aeb634a37172b 22:10:56.323 INFO - Got result: OK on session adbf7bd6cfc040b8ad3aeb634a37172b 22:10:56.325 INFO - Command request: waitForPageToLoad[7000, ] on session adbf7bd6cfc040b8ad3aeb634a37172b 22:11:03.345 INFO - Got result: Timed out after 7000ms on session adbf7bd6cfc040b8ad3aeb634a37172b 22:11:03.367 INFO - Command request: testComplete[, ] on session adbf7bd6cfc040b8ad3aeb634a37172b 22:11:03.367 INFO - Killing Firefox... 22:11:03.447 INFO - Got result: OK on session adbf7bd6cfc040b8ad3aeb634a37172b
The JBehave Maven Plugin
The JBehave Maven Plugin allows you to access the Embedder functionality using Maven goals:
- map-stories-as-embeddables
- map-stories-as-paths
- run-stories-as-embeddables
- run-stories-as-paths
- run-stories-with-annotated-embedder
- report-stepdocs
- report-stepdocs-as-embeddables
- generate-stories-view
- unpack-view-resources
For more detailed information, take a look at the plugin’s documentation.
Sources Download
I have put the source from this tutorial on my Bitbucket repository – download it there or check it out using Git:
git clone https://bitbucket.org/hascode/jbehave-tutorial.git
Troubleshooting
- “com.thoughtworks.selenium.SeleniumException: ERROR Server Exception: sessionId should not be null; has this session been started yet?” – ensure that the firefox binary is in your path .. e.g. export PATH=$PATH:/usr/lib/dir-to-firefox/. Another way to specify where your browser is located to define in in the selenium instance .. e.g.: selenium = new DefaultSelenium(“127.0.0.1″, 4444, “*chrome /path/to-your-browser”, “http://www.google.de/”);
- Elements not found in selenium – ensure that the page is loaded .. use waitForPageToLoad() , waitForCondition() or waitForPopup() .. ajax calls are a problem here and Thread.sleep() is a dirty hack .. but there are workarounds on the internet.
Alternative: Cucumber for Java and Java EE
If you’re interested in an alternative please feel free to have a look at the following article of mine: “BDD Testing with Cucumber, Java and JUnit“.
It is also possible to integrate Cucumber in a complex Java EE project with Arquillian and the Cukespace extension – please feel free to have a look at another article of mine: “Marrying Java EE and BDD with Cucumber, Arquillian and Cukespace“.
Resources
- JBehave: Getting started
- Behaviour-Drive.org: Introduction into BDD
- IBM Developerworks: In pursuit of code quality: Adventures in behavior-driven development
- Dzone.com: Continuous Testing with Selenium and JBehave using Page Objects
- JBehave tutorial on Github
- Nicolas Frankel: A Java geek – Automate your integration tests
- JBehave: Using Selenium
- Dan North: Introducing BDD
- JBehave Maven Plugin
- JBehave Web
- Selenium Server Download
- Selenium Tips&Tricks
- Keep on Moving Blog: Behavior Driven Development (german language)
BDD Articles of mine
The following articles of mine are covering different aspects and frameworks for Behaviour Driven Development:
- BDD Testing with Cucumber, Java and JUnit
- Writing BDD-Style Webservice Tests with Karate and Java
- Marrying Java EE and BDD with Cucumber, Arquillian and Cukespace
- A short Introduction to ScalaTest
- Running JavaScript Tests with Maven, Jasmine and PhantomJS
Article Updates
- 2014-12-28: Link to my article about the Cucumber framework added.
- 2015-01-07: Link to my article about Cucumber in a Java EE environment and the cukespace extension added.
- 2017-04-06: Links to other BDD articles of mine added.
Tags: acceptance test, agile, bdd, behavior, ddd, jbehave, scenario, selenium, story, tdd, testing, tutorial, xp
June 14th, 2011 at 6:27 pm
I tried running your examples but for some reason when i try to execute the test case i get 0 stories and 0 scnearios output. Can you tell me why ?
Scenario: When users are added to the repository they must be found afterwards in the repository
Given a user repository
When the user adds 3 users to the user repository
Then the user repository must contain 3 users
log4j:WARN No appenders could be found for logger (freemarker.cache).
log4j:WARN Please initialize the log4j system properly.
(AfterStories)
Reports view generated with 0 stories (of which 0 pending) containing 0 scenarios (of which 0 failed and 0 pending)
PASSED: run
July 28th, 2011 at 3:59 pm
you should rename your story file from add_product_to_shopping_cast.story to add_product_to_shopping_cart.story, then the jbehave will find the tests…
July 28th, 2011 at 4:55 pm
thanks for your remark, typo fixed! :)
August 11th, 2011 at 2:04 pm
Hi, thanks for your explanation.
Is it possible to use the webqueuerunner and selenium together?
Thanks.
August 17th, 2011 at 1:31 pm
Hi Micha,
1) Could you please tell me how do we download and configure for eclipse?
* Apache Maven >=2.1
2) Is it ok if i use JDK 1.6 or so?
3) Can we use multiple scenarios in one scenario file and multiple examples for each scenario in one file?
Thanks,
Saanvi
September 19th, 2011 at 8:33 am
I’ve just downloaded your example. It works but jbehave says “Reports view generated with 0 stories”
I also checked story files; Names seems ok
add_product_to_shopping_cart.story
Any suggestions ?
Thanks.
September 19th, 2011 at 12:54 pm
I found the problem
new StoryReporterBuilder().withFormats(Format.CONSOLE));
When i change it to new StoryReporterBuilder().withFormats(Format.HTML));
İt generates html reports but still it reports are empty.
Suggestions ?
Thanks.
November 10th, 2011 at 5:33 pm
Hi!
I’m trying to follow your tutorial, but I stick at first execution. When I run:
mvn test
… I got:
——————————————————-
T E S T S
——————————————————-
There are no tests to run.
Results :
Tests run: 0, Failures: 0, Errors: 0, Skipped: 0
[INFO] ————————————————————————
[INFO] BUILD SUCCESSFUL
[INFO] ————————————————————————
[INFO] Total time: 3 seconds
[INFO] Finished at: Thu Nov 10 15:31:16 BRST 2011
[INFO] Final Memory: 10M/42M
[INFO] ————————————————————————
… instead of that first output.
Please, can you help me? My project is exactly the same, no tipos, no changed names, and I followed your instructions step-by-step.
Thanks in advance,
Yorga.
October 18th, 2012 at 9:29 am
Hi,
I am unable to use maven. May be because i am new to JAVA. But i have used selenium through c# language, and i dont have any problem in it. But now i want to use selenium and Jbehave in eclipse using JAVA language. But every where i see for references it says to configure Maven and i am having problem in it.
So can you please give the detailed steps how to configure Maven.
Thanks
October 18th, 2012 at 5:17 pm
If really don’t want to use Maven then you could alternatively download each dependency as jar file from the public maven repository .. e.g. from http://mvnrepository.com/.
Otherwise you could download and install maven on your system as documented on http://maven.apache.org/ und build the project in the shell.
You could also take a look the maven integration offered for the most popular Java IDEs like Eclipse, NetBeans or IntelliJ IDE.
Do you have a specific question regarding Maven?
April 16th, 2013 at 2:42 am
I just want to know,how calls are made for html report file which user see as report of story execution?
I want to update html-output.ftl file such a way that when final report gets generated in
..\target\jbehave\view\reports.html file, i don’t want columns like Given-story Scenarios, Steps in html output file as I want to delete those permanently ..how i can do that?
When i see the html-output.ftl file it has very complex code which does not get how the columns are mapped and how to delete the what code…
…do I need to delete the code in jbehave-core.css file
as well? Pls suggest
Also if i made changes in code , how i can make them persistent/permanent so that every time i compile, i don’t see old code…
December 11th, 2013 at 8:36 am
Hi Lurtz,
In order to see stats in HTML, You need to also add Format.STATS to StoryReporterBuilder .
Below is the code that i used –
@Override
public Configuration configuration() {
return new MostUsefulConfiguration()
.useStoryLoader(new LoadFromClasspath(getClass().getClassLoader()))
.useStoryReporterBuilder(new StoryReporterBuilder() .withFormats(Format.HTML, Format.STATS) .withFailureTrace(true) .withFailureTraceCompression(true));
}
December 11th, 2013 at 12:00 pm
Hi Micha Kops,
You have expained it really well.. Thanks for sharing this.
–Regards
Deepak jain
February 27th, 2014 at 7:48 am
Excellent! Keep it up. :-D
June 30th, 2014 at 8:16 am
Hi Micha Kops,
You have expained it really well… but i hvee one doubt is JBehave frame work is related to testing side or development side.
July 3rd, 2014 at 6:38 pm
Hi Raju,
thanks for your feedback! Could you please clarify your position in detail? :)
Cheers,
Micha
September 15th, 2014 at 4:15 pm
Hi,
I keep getting Unable to create application of class com.hascode.tutorial.runner.TutorialRunnerApplication exception when i try to access the local host on starting up the jetty server.
Kindly help.
July 22nd, 2015 at 12:21 pm
Great job. I am trying it out with Netbeans using JBehave Plugin. Everything works, but on the add_user_to_repository.story – first two lines (GIVEN) are mapped to the step correctly, but the last 2 lines (WHEN THEN) are NOT:
Given 3 products
Given a user repository
When the user adds 3 users to the user repository
Then the user repository must contain 3 users
Any suggestions?
Thanks.
Mahesh
September 17th, 2016 at 7:46 am
Hi,
I am trying to run and understand the sample project. I just downloaded it and make another simple scenario to add_product_to_shopping_cart.story and also add the corresponding methods to AddProductToShoppingCart (in my case – it is only one new method, the other are commen with the other scenario). When I right-click on the project and select Run As –> JUnit Test the result is only one run for AddProductToShoppingCart, despite the fact that I have 2 tests there. Do I make sth wrong? Is there some othre way to get more detailed information about the fails?
Thanks,
Aleksander