AiL – Simplest JBehave Scenario

In the last segment, I managed to get JBehave reporting under Maven using a pre-canned example. This time, I want to tackle the other extreme – I want to develop a single story in JBehave and see what’s the bare minimum it takes to get it running, inside an IDE (in my case, Eclipse)/

There’s a bunch of different ways to run JBehave, but the easiest looks to be leveraging JUnit. They even provide a base class to inherit from – JUnitStory. The simplest possible JUnitStory class looks like this:

package net.twasink.jbehave.simple.stories;

import org.jbehave.core.junit.JUnitStory;

public class CookBaconStory extends JUnitStory {
}

And that will work. Kind of. It will look for a file called 'cook_bacon_story.story' in the same package on the classpath, and run it. Assuming a story like this one:

Narrative:

In order to learn how to use JBehave better
As a developer of some middling-level of competence
I want to write some example stories about cooking bacon

Scenario: Cooking bacon in a frypan
Given 12 strips of bacon
And a frypan on a medium heat
When I put the bacon into the frypan
And wait for 10 minutes or until the bacon sizzles
And flip the bacon
And wait for 5 minutes
Then I get perfectly cooked crispy bacon

Then I get output like this:

Processing system properties {}
Using controls EmbedderControls[batch=false,skip=false,generateViewAfterStories=true,ignoreFailureInStories=false,ignoreFailureInView=false,verboseFailures=false,verboseFiltering=false,storyTimeoutInSecs=300,threads=1]
Running story net/twasink/jbehave/simple/stories/cook_bacon_story.story
Generating reports view to '/Users/robertdw/personal/jbehave-spike/jbehave-simple-story/target/jbehave' using formats '[]' 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 0 stories (of which 0 pending) containing 0 scenarios (of which 0 pending)

Hmm. It has found the story, and run it – you even get an error if the story file doesn’t exist. But the bit at the end says “generated with 0 stories (of which 0 pending) containing 0 scenarios (of which 0 pending)”. Doesn’t sound good.

I dunno – you think that the basic infrastructure would report the stories that it finds. Anyway, I know that the full example works – so I take that and start paring it back until I get some output that makes sense. This is what I ended up with:


package net.twasink.jbehave.simple.stories;

import org.jbehave.core.configuration.Configuration;
import org.jbehave.core.configuration.MostUsefulConfiguration;
import org.jbehave.core.junit.JUnitStory;

public class CookBaconStory extends JUnitStory {

    @Override  public Configuration configuration() {
        return new MostUsefulConfiguration();
    }
}

And now my output looks like this:


Processing system properties {}
Using controls EmbedderControls[batch=false,skip=false,generateViewAfterStories=true,ignoreFailureInStories=false,ignoreFailureInView=false,verboseFailures=false,verboseFiltering=false,storyTimeoutInSecs=300,threads=1]
Running story net/twasink/jbehave/simple/stories/cook_bacon_story.story
Generating reports view to '/Users/robertdw/personal/jbehave-spike/jbehave-simple-story/target/jbehave' using formats '[]' 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 1 stories (of which 1 pending) containing 1 scenarios (of which 1 pending)

So now, using the ‘most useful configuration’, I’ve got a report view that actually condescends to admit that my scenario exists.

The next step is to add some, well, steps. With JBehave, when using the JUnitStory anyway, you do this by overriding the stepsFactory() method. Now my code looks like this:

package net.twasink.jbehave.simple.stories;

import net.twasink.jbehave.simple.steps.BaconCookingSteps;

import org.jbehave.core.configuration.Configuration;
import org.jbehave.core.configuration.MostUsefulConfiguration;
import org.jbehave.core.junit.JUnitStory;
import org.jbehave.core.steps.InjectableStepsFactory;
import org.jbehave.core.steps.InstanceStepsFactory;

public class CookBaconStory extends JUnitStory {

    @Override public Configuration configuration() {
        return new MostUsefulConfiguration();
    }

    @Override public InjectableStepsFactory stepsFactory() {
        return new InstanceStepsFactory(configuration(),
                new BaconCookingSteps());
    }
}

My BaconCookingSteps class isn’t complicated – writing steps isn’t the point of this spike. Here it is:

package net.twasink.jbehave.simple.steps;

import org.jbehave.core.annotations.Given;
import org.jbehave.core.annotations.Then;
import org.jbehave.core.annotations.When;

public class BaconCookingSteps {
    @Given("12 strips of bacon")
    public void howManyStripsOfBacon() {
        System.out.println(">>>> you're making how many pieces of bacon?");
    }

    @Given("a frypan on a medium heat")
    public void heatTheFrypan() {
        System.out.println(">>>> how hot did you want that?");
    }

    @When("I put the bacon into the frypan")
    public void startCooking() {
        System.out.println(">>>> Yeah - put that stuff there");
    }

    @When("wait for 10 minutes or until the bacon sizzles")
    public void cookUntilSatisifed() {
        System.out.println(">>>> I'm hungry! Why are we waiting?");
    }

    @When("flip the bacon")
    public void flipBacon() {
        System.out.println(">>>> These aren't panckes!");
    }

    @When("wait for 5 minutes")
    public void cookSomeMore() {
        System.out.println(">>>> More waiting?");
    }

    @Then("I get perfectly cooked crispy bacon")
    public void nomNomNom() {
        System.out.println(">>>> Finally! Nom nom nom nom");
    }
}

And when I run this, I get this output:

Processing system properties {}
Using controls EmbedderControls[batch=false,skip=false,generateViewAfterStories=true,ignoreFailureInStories=false,ignoreFailureInView=false,verboseFailures=false,verboseFiltering=false,storyTimeoutInSecs=300,threads=1]
Running story net/twasink/jbehave/simple/stories/cook_bacon_story.story
>>>> you're making how many pieces of bacon?
>>>> how hot did you want that?
>>>> Yeah - put that stuff there
>>>> I'm hungry! Why are we waiting?
>>>> These aren't panckes!
>>>> More waiting?
>>>> Finally! Nom nom nom nom
Generating reports view to '/Users/robertdw/personal/jbehave-spike/jbehave-simple-story/target/jbehave' using formats '[]' 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 1 stories (of which 1 pending) containing 1 scenarios (of which 1 pending)

So – that’s how you write a bare minimum JBehave story to run inside an IDE. This isn’t too complicated – after all, I can always add a new base class to hold that “most useful configuration” bit. JUnitStory itself isn’t much bigger, either – less than 10 lines of code, really. Similarly, if I really wanted to, I could combine the Story and the Steps together into one class, wire it all up in the parent, and reduce my overhead to making my POJO steps extend a parent class.

All of that sounds pretty good. So what’s the problem? Well, the problem is how it looks in the IDE.

That’s just crap – the test scenarios don’t show up as tests. The JUnitStory, as far as JUnit is concerned, is one test. That story file may have a thousand scenarios – it’s still one test. That’s not acceptable, IMHO – we’ll have to do something about that later.

But not now. For now, I’ve worked out what a minimal story looks like. Before I start solving new problems, I want to solve the next thing on my list: using dependency injection (in particular, Spring). That will be my next post.

Author: Robert Watkins

My name is Robert Watkins. I am a software developer and have been for over 20 years now. I currently work for people, but my opinions here are in no way endorsed by them (which is cool; their opinions aren’t endorsed by me either). My main professional interests are in Java development, using Agile methods, with a historical focus on building web based applications. I’m also a Mac-fan and love my iPhone, which I’m currently learning how to code for. I live and work in Brisbane, Australia, but I grew up in the Northern Territory, and still find Brisbane too cold (after 22 years here). I’m married, with two children and one cat. My politics are socialist in tendency, my religious affiliation is atheist (aka “none of the above”), my attitude is condescending and my moral standing is lying down.

13 thoughts on “AiL – Simplest JBehave Scenario”

  1. In the course of coaching and teaching BDD I’ve introduced a lot of people to this page and they all have had nothing but praise for your simple, yet informative description of the subject – the last person make the following comment: “I wish JBehave web site could outsource the documentation to Robert – he couldn’t do any worse than them!!!” Thank you on behalf of many people.

  2. Hi, I have read all your JBehave posts and you make some very valid points. Was just interested if you found a way to display all scenarios in a junit test?

  3. Hi there,
    I’m trying your example but when I run the test I always get the following output:
    org.jbehave.core.io.StoryResourceNotFound: Story path ‘com/jbehave/cook_bacon_story.story’ not found by class

    As I see, everything is in place and I don’t know where to look. Could you give me a point?
    Thanks

    1. Marius, you haven’t put the file on the classpath correctly. Make sure you have a file called ‘cook_bacon_story.story’ in the same package as the test.

  4. Hi Robert

    Hope you are still looking after this blog.
    I used your example but always got this exception:
    java.lang.NoClassDefFoundError: com/thoughtworks/paranamer/Paranamer
    at org.jbehave.core.ConfigurableEmbedder.(ConfigurableEmbedder.java:40)
    at org.jbehave.core.junit.JUnitStory.(JUnitStory.java:16)
    at net.twasink.jbehave.simple.stories.CookBaconStory.(CookBaconStory.java:12)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:526)
    at org.junit.runners.BlockJUnit4ClassRunner.createTest(BlockJUnit4ClassRunner.java:195)
    at org.junit.runners.BlockJUnit4ClassRunner$1.runReflectiveCall(BlockJUnit4ClassRunner.java:244)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.BlockJUnit4ClassRunner.methodBlock(BlockJUnit4ClassRunner.java:241)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
    Caused by: java.lang.ClassNotFoundException: com.thoughtworks.paranamer.Paranamer
    at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:425)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:358)
    … 25 more

    1. William, I hope you’ve managed to solve your problem. `ClassdefNotFound` is usually a secondary exception, in response to an error while loading a class – so I’d look for an earlier error.

  5. Hello, did you have any spare time to work on better integration of jBehave with Junit features?

    Upfront thanks for your reponse,
    Cheers!

    1. I have solved this problem: you should add all the dependent libraries to you Eclipse project (it is about 80 *.jar files for jbehave 3.9.5). All of them can be downloaded from Maven Central Repository using http://jbehave.org/reference/stable/dependencies.html manual(one should know what is ANT and Maven). But, yes jbehave manual is very tricky for beginners.

Leave a comment