Oh JBehave, Baby! Behaviour Driven Development using JBehave

May 31st, 2011 by

Behaviour Driven Development Keyword Map

Behaviour 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.

 

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:

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

BDD Articles of mine

The following articles of mine are covering different aspects and frameworks for Behaviour Driven Development:

    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: , , , , , , , , , , , ,

    19 Responses to “Oh JBehave, Baby! Behaviour Driven Development using JBehave”

    1. Randy Says:

      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

    2. miss type Says:

      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…

    3. micha kops Says:

      thanks for your remark, typo fixed! :)

    4. Ivan V Says:

      Hi, thanks for your explanation.
      Is it possible to use the webqueuerunner and selenium together?

      Thanks.

    5. Saanvi Says:

      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

    6. Lurtz Says:

      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.

    7. Lurtz Says:

      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.

    8. Yorga Says:

      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.

    9. Krishna Says:

      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

    10. micha kops Says:

      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?

    11. Kaustubh Says:

      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…

    12. Deepak Jain Says:

      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));
      }

    13. Deepak Jain Says:

      Hi Micha Kops,
      You have expained it really well.. Thanks for sharing this.

      –Regards
      Deepak jain

    14. Pure Logic Edmonton Says:

      Excellent! Keep it up. :-D

    15. raju Says:

      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.

    16. micha kops Says:

      Hi Raju,

      thanks for your feedback! Could you please clarify your position in detail? :)

      Cheers,

      Micha

    17. Sat Says:

      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.

    18. Mahesh Says:

      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

    19. Aleksander Says:

      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

    Search
    Tags
    Categories