JBehave Configuration Tutorial - codecentric AG Blog

:

I love JBehave. It’s a great test automation framework that takes full advantage of all the possibilities of the JVM and the plethora of libraries that are available for Java. JBehave makes the transition from natural language style BDD-tests to Java methods incredibly quick, it’s just an annotation away. But … I hate to say it, but there’s a big “BUT” here … it is more often than not a little bit too flexible and configurable. So much that you tend to lose overview and understand. This is why I would like to give you a hand and guide you through the first steps.

So, what does it take to do some test automation? Three things: a test, something that automates the test, and something that runs the test. So, let me present to you the simplest and shortest way to run a test with JBehave. I will then show step for step how to make the setup more flexible, and explain why you should do it.

JBehave KISSed

The Test

Scenario: 2 squared

Given a variable x with value 2
When I multiply x by 2 
Then x should equal 4

Scenario: 3 squared

Given a variable x with value 3
When I multiply x by 3 
Then x should equal 10

The test will stay the same for the rest of the examples. It’s quite forward, and there’s possible a mistake in one of the two scenarios.

Steps

The thingies necessary to let JBehave know what exactly it should do when it encounters “Given a variable x with value 2” are called StepCandidates. The easiest way is to have a set of methods in a class and annotate them with @Given, @When or @Then.

package de.codecentric.simplejbehave._1_kiss;
 
import org.jbehave.core.annotations.Given;
import org.jbehave.core.annotations.Named;
import org.jbehave.core.annotations.Then;
import org.jbehave.core.annotations.When;
import org.jbehave.core.steps.Steps;
 
public class ExampleSteps extends Steps {
	int x;
 
	@Given("a variable x with value $value")
	public void givenXValue(@Named("value") int value) {
		x = value;
	}
 
	@When("I multiply x by $value")
	public void whenImultiplyXBy(@Named("value") int value) {
		x = x * value;
	}
 
	@Then("x should equal $value")
	public void thenXshouldBe(@Named("value") int value) {
		if (value != x)
			throw new RuntimeException("x is " + x + ", but should be " + value);
	}
}

package de.codecentric.simplejbehave._1_kiss;import org.jbehave.core.annotations.Given; import org.jbehave.core.annotations.Named; import org.jbehave.core.annotations.Then; import org.jbehave.core.annotations.When; import org.jbehave.core.steps.Steps;public class ExampleSteps extends Steps { int x;@Given("a variable x with value $value") public void givenXValue(@Named("value") int value) { x = value; }@When("I multiply x by $value") public void whenImultiplyXBy(@Named("value") int value) { x = x * value; }@Then("x should equal $value") public void thenXshouldBe(@Named("value") int value) { if (value != x) throw new RuntimeException("x is " + x + ", but should be " + value); } }

Note that the class currently extends from Steps. This is necessary because of the way, we will use the steps and will become obsolete later. I’ll show You, just a second.

Test Execution

Missing now is only something that looks for the story test, finds the Steps and executes the test. So this is how you do that in the most simple way:

package de.codecentric.simplejbehave._1_kiss;
 
import java.util.Arrays;
import java.util.List;
 
import org.jbehave.core.embedder.Embedder;
 
public class SimpleJBehave {
 
	private static Embedder embedder = new Embedder();
	private static List<String> storyPaths = Arrays
			.asList("de/codecentric/simplejbehave/Math.story");
 
	public static void main(String[] args) {
		embedder.candidateSteps().add(new ExampleSteps());
		embedder.runStoriesAsPaths(storyPaths);
	}
}

package de.codecentric.simplejbehave._1_kiss;import java.util.Arrays; import java.util.List;import org.jbehave.core.embedder.Embedder;public class SimpleJBehave {private static Embedder embedder = new Embedder(); private static List<String> storyPaths = Arrays .asList("de/codecentric/simplejbehave/Math.story");public static void main(String[] args) { embedder.candidateSteps().add(new ExampleSteps()); embedder.runStoriesAsPaths(storyPaths); } }

You have a classic Java main method. It uses an Embedder, which is JBehave’s main entry point with a really bad name (But it is not alone with its fate of a badly chosen name, there are more in the flock of badly named classes in JBehave…) The Embedder is designed to embed JBehave in all sorts of IDEs, test frameworks, you name it. In our case, we embed JBehave in nothing, use it bare naked.

There are two things, that we have to tell JBehave. First: Where are your steps. For that, we add an instance of our class to the list of candidate steps. And then, we have to tell JBehave which story to run. This is as simple, as passing a list of strings to a method which runs that stories.

This is it. Don’t believe me? Try it out yourself, you should get this as an 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 de/codecentric/simplejbehave/Math.story
Generating reports view to 'C:\cc\workspace-git\SimpleJBehave\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 0 pending) containing 2 scenarios (of which 0 pending)
Exception in thread "main" org.jbehave.core.embedder.Embedder$RunningStoriesFailed: Failures in running stories: ReportsCount[stories=1,storiesNotAllowed=0,storiesPending=0,scenarios=2,scenariosFailed=1,scenariosNotAllowed=0,scenariosPending=0,stepsFailed=1]
	at org.jbehave.core.embedder.Embedder$ThrowingRunningStoriesFailed.handleFailures(Embedder.java:499)
	at org.jbehave.core.embedder.Embedder.handleFailures(Embedder.java:265)
	at org.jbehave.core.embedder.Embedder.generateReportsView(Embedder.java:252)
	at org.jbehave.core.embedder.Embedder.generateReportsView(Embedder.java:233)
	at org.jbehave.core.embedder.Embedder.runStoriesAsPaths(Embedder.java:212)
	at de.codecentric.simplejbehave._1_kiss.SimpleJBehave.main(SimpleJBehave.java:16)

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 de/codecentric/simplejbehave/Math.story Generating reports view to 'C:\cc\workspace-git\SimpleJBehave\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 0 pending) containing 2 scenarios (of which 0 pending) Exception in thread "main" org.jbehave.core.embedder.Embedder$RunningStoriesFailed: Failures in running stories: ReportsCount[stories=1,storiesNotAllowed=0,storiesPending=0,scenarios=2,scenariosFailed=1,scenariosNotAllowed=0,scenariosPending=0,stepsFailed=1] at org.jbehave.core.embedder.Embedder$ThrowingRunningStoriesFailed.handleFailures(Embedder.java:499) at org.jbehave.core.embedder.Embedder.handleFailures(Embedder.java:265) at org.jbehave.core.embedder.Embedder.generateReportsView(Embedder.java:252) at org.jbehave.core.embedder.Embedder.generateReportsView(Embedder.java:233) at org.jbehave.core.embedder.Embedder.runStoriesAsPaths(Embedder.java:212) at de.codecentric.simplejbehave._1_kiss.SimpleJBehave.main(SimpleJBehave.java:16)

Of course there is much defaulting now happening under the hood. We will come to that in a moment. For now you should remember, that JBehave basically needs two things: your tests and your steps. Everything else is just nice to make everything more flexible.

Skip the main, use JUnit

The first thing we want to get rid of is our own main method. We will rather use another class that comes with JBehave: JUnitStories. This class takes all the stories you pass in and runs them with JUnit. The benefit is, that there are many, many tools available integrate with JUnit (make it run, parse the result), which makes it my preferred way of running JBehave tests.

package de.codecentric.simplejbehave._2_junit;
 
import java.util.Arrays;
import java.util.List;
 
import org.jbehave.core.junit.JUnitStories;
 
public class SimpleJBehave extends JUnitStories {
 
	public SimpleJBehave() {
		super();
		this.configuredEmbedder().candidateSteps().add(new ExampleSteps());
	}
 
	@Override
	protected List<String> storyPaths() {
		return Arrays.asList("de/codecentric/simplejbehave/Math.story");
	}
}

package de.codecentric.simplejbehave._2_junit;import java.util.Arrays; import java.util.List;import org.jbehave.core.junit.JUnitStories;public class SimpleJBehave extends JUnitStories {public SimpleJBehave() { super(); this.configuredEmbedder().candidateSteps().add(new ExampleSteps()); }@Override protected List<String> storyPaths() { return Arrays.asList("de/codecentric/simplejbehave/Math.story"); } }

What has changed? We extend from JUnitStories. For that we have to implement an abstract method storyPaths that returns all our tests. So we have our stories covered. What was the other thing JBehave needs? Our steps. Right. There’s still an Embedder hidden in the JUnitStories, and we can get to it via the configuredEmbedder() method.

Apart from that, we changed nothing, but can now use JUnit to run our test. Awesome!

Ok, I see what you are seeing. Just having a single run method for all the tests there, is not what you want. I agree, and there’s a solution.

Introducing a StepsFactory

In order to make that solution work, we should use a more conventional way of providing the steps to JBehave. Usually you don’t force them down to JBehave by adding them to the embedder, but you provide a steps factory. This has the advantage that you can later use dependency injection frameworks like Spring or Guice to wire your step classes. But I am running ahead. With the introduction of the step factory, two things change: We can get rid of extending from Step in our Steps:

package de.codecentric.simplejbehave._3_stepsfactory;
 
import org.jbehave.core.annotations.Given;
import org.jbehave.core.annotations.Named;
import org.jbehave.core.annotations.Then;
import org.jbehave.core.annotations.When;
 
public class ExampleSteps {
	int x;
 
	@Given("a variable x with value $value")
	public void givenXValue(@Named("value") int value) {
		x = value;
	}
 
	@When("I multiply x by $value")
	public void whenImultiplyXBy(@Named("value") int value) {
		x = x * value;
	}
 
	@Then("x should equal $value")
	public void thenXshouldBe(@Named("value") int value) {
		if (value != x)
			throw new RuntimeException("x is " + x + ", but should be " + value);
	}
 
}

package de.codecentric.simplejbehave._3_stepsfactory;import org.jbehave.core.annotations.Given; import org.jbehave.core.annotations.Named; import org.jbehave.core.annotations.Then; import org.jbehave.core.annotations.When;public class ExampleSteps { int x;@Given("a variable x with value $value") public void givenXValue(@Named("value") int value) { x = value; }@When("I multiply x by $value") public void whenImultiplyXBy(@Named("value") int value) { x = x * value; }@Then("x should equal $value") public void thenXshouldBe(@Named("value") int value) { if (value != x) throw new RuntimeException("x is " + x + ", but should be " + value); }}

The class with the StepCandidates is now a true Plain Old Java Class. Yay 🙂 Next, let JBehave know how to create new steps.

package de.codecentric.simplejbehave._3_stepsfactory;
 
import java.util.Arrays;
import java.util.List;
 
import org.jbehave.core.junit.JUnitStories;
import org.jbehave.core.steps.InjectableStepsFactory;
import org.jbehave.core.steps.InstanceStepsFactory;
import org.junit.runner.RunWith;
 
import de.codecentric.jbehave.junit.monitoring.JUnitReportingRunner;
 
@RunWith(JUnitReportingRunner.class)
public class SimpleJBehave extends JUnitStories {
 
	public SimpleJBehave() {
		super();
	}
 
	@Override
	public InjectableStepsFactory stepsFactory() {
		return new InstanceStepsFactory(configuration(), new ExampleSteps());
	}
 
	@Override
	protected List<String> storyPaths() {
		return Arrays.asList("de/codecentric/simplejbehave/Math.story");
	}
}

package de.codecentric.simplejbehave._3_stepsfactory;import java.util.Arrays; import java.util.List;import org.jbehave.core.junit.JUnitStories; import org.jbehave.core.steps.InjectableStepsFactory; import org.jbehave.core.steps.InstanceStepsFactory; import org.junit.runner.RunWith;import de.codecentric.jbehave.junit.monitoring.JUnitReportingRunner;@RunWith(JUnitReportingRunner.class) public class SimpleJBehave extends JUnitStories {public SimpleJBehave() { super(); }@Override public InjectableStepsFactory stepsFactory() { return new InstanceStepsFactory(configuration(), new ExampleSteps()); }@Override protected List<String> storyPaths() { return Arrays.asList("de/codecentric/simplejbehave/Math.story"); } }

The simplest InjectableStepsFactory class you can use is the InstanceStepsFactory, which you just have to provide with a list of objects that contain annotated methods. Another thing you have to pass into the steps factory is on ominous configuration. We will see in the next example what that is exactly, so for now, we just call a method from a super class and live with whatever configuration we get.

With all that setup, we can leverage a nice tool, that wires the JBehave monitoring capabilities together with JUnit notifications. It is called jbehave-junit-runner, and the only thing you have to do is to let JUnit know to use the class, by declaring it with the @RunWith annotation. In short you now get this as a result:

Now, wow, we could stop here. You can execute JBehave tests and can leverage all great JUnit accessories out there. But we don’t 🙂

MostUsefulConfiguration

One of the better named classes in JBehave is a subclass of Configuration named MostUsefulConfiguration. I will show you what are all the defaults that you get, when you don’t specify anything extraordinary, like we did so far. With this runner, you get the same result as above, but you have to write a lot more code 🙂

package de.codecentric.simplejbehave._4_configuration;
 
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
 
import org.jbehave...
 
import org.junit.runner.RunWith;
 
import com.thoughtworks.paranamer.NullParanamer;
 
import de.codecentric.jbehave.junit.monitoring.JUnitReportingRunner;
 
@RunWith(JUnitReportingRunner.class)
public class SimpleJBehave extends JUnitStories {
 
	private Configuration configuration;
 
	public SimpleJBehave() {
		super();
		configuration = new Configuration() {
		};
 
		// configuration.doDryRun(false); "no dry run" is implicit by using
		// default StoryControls
 
		// configuration.useDefaultStoryReporter(new ConsoleOutput());
		// deprecated -- rather use StoryReportBuilder
 
		configuration.useFailureStrategy(new RethrowingFailure());
		configuration.useKeywords(new LocalizedKeywords(Locale.ENGLISH));
		configuration.usePathCalculator(new AbsolutePathCalculator());
		configuration.useParameterControls(new ParameterControls());
		configuration.useParameterConverters(new ParameterConverters());
		configuration.useParanamer(new NullParanamer());
		configuration.usePendingStepStrategy(new PassingUponPendingStep());
		configuration.useStepCollector(new MarkUnmatchedStepsAsPending());
		configuration.useStepdocReporter(new PrintStreamStepdocReporter());
		configuration.useStepFinder(new StepFinder());
		configuration.useStepMonitor(new SilentStepMonitor());
		configuration
				.useStepPatternParser(new RegexPrefixCapturingPatternParser());
		configuration.useStoryControls(new StoryControls());
		configuration.useStoryLoader(new LoadFromClasspath());
		configuration.useStoryParser(new RegexStoryParser(configuration
				.keywords()));
		configuration.useStoryPathResolver(new UnderscoredCamelCaseResolver());
		configuration.useStoryReporterBuilder(new StoryReporterBuilder());
		configuration.useViewGenerator(new FreemarkerViewGenerator());
 
		EmbedderControls embedderControls = configuredEmbedder()
				.embedderControls();
		embedderControls.doBatch(false);
		embedderControls.doGenerateViewAfterStories(true);
		embedderControls.doIgnoreFailureInStories(false);
		embedderControls.doIgnoreFailureInView(false);
		embedderControls.doSkip(false);
		embedderControls.doVerboseFailures(false);
		embedderControls.doVerboseFiltering(false);
		embedderControls.useStoryTimeoutInSecs(300);
		embedderControls.useThreads(1);
	}
 
	@Override
	public Configuration configuration() {
		return configuration;
	}
 
	@Override
	public InjectableStepsFactory stepsFactory() {
		return new InstanceStepsFactory(configuration(), new ExampleSteps());
	}
 
	@Override
	protected List<String> storyPaths() {
		return Arrays.asList("de/codecentric/simplejbehave/Math.story");
	}
}

package de.codecentric.simplejbehave._4_configuration;import java.util.Arrays; import java.util.List; import java.util.Locale;import org.jbehave...import org.junit.runner.RunWith;import com.thoughtworks.paranamer.NullParanamer;import de.codecentric.jbehave.junit.monitoring.JUnitReportingRunner;@RunWith(JUnitReportingRunner.class) public class SimpleJBehave extends JUnitStories {private Configuration configuration;public SimpleJBehave() { super(); configuration = new Configuration() { };// configuration.doDryRun(false); "no dry run" is implicit by using // default StoryControls// configuration.useDefaultStoryReporter(new ConsoleOutput()); // deprecated -- rather use StoryReportBuilderconfiguration.useFailureStrategy(new RethrowingFailure()); configuration.useKeywords(new LocalizedKeywords(Locale.ENGLISH)); configuration.usePathCalculator(new AbsolutePathCalculator()); configuration.useParameterControls(new ParameterControls()); configuration.useParameterConverters(new ParameterConverters()); configuration.useParanamer(new NullParanamer()); configuration.usePendingStepStrategy(new PassingUponPendingStep()); configuration.useStepCollector(new MarkUnmatchedStepsAsPending()); configuration.useStepdocReporter(new PrintStreamStepdocReporter()); configuration.useStepFinder(new StepFinder()); configuration.useStepMonitor(new SilentStepMonitor()); configuration .useStepPatternParser(new RegexPrefixCapturingPatternParser()); configuration.useStoryControls(new StoryControls()); configuration.useStoryLoader(new LoadFromClasspath()); configuration.useStoryParser(new RegexStoryParser(configuration .keywords())); configuration.useStoryPathResolver(new UnderscoredCamelCaseResolver()); configuration.useStoryReporterBuilder(new StoryReporterBuilder()); configuration.useViewGenerator(new FreemarkerViewGenerator());EmbedderControls embedderControls = configuredEmbedder() .embedderControls(); embedderControls.doBatch(false); embedderControls.doGenerateViewAfterStories(true); embedderControls.doIgnoreFailureInStories(false); embedderControls.doIgnoreFailureInView(false); embedderControls.doSkip(false); embedderControls.doVerboseFailures(false); embedderControls.doVerboseFiltering(false); embedderControls.useStoryTimeoutInSecs(300); embedderControls.useThreads(1); }@Override public Configuration configuration() { return configuration; }@Override public InjectableStepsFactory stepsFactory() { return new InstanceStepsFactory(configuration(), new ExampleSteps()); }@Override protected List<String> storyPaths() { return Arrays.asList("de/codecentric/simplejbehave/Math.story"); } }

Phew. That’s quite an amount of configuration to digest. And for that reason, we won’t digest it now, but in a later blog post. What I wanted to show you here, is all the possibilities that you have. Sometimes the effects and of the configuration are interlinked with each other. We will take a closer look into all that in the future. But for now remember this:

1) It is very important, that your overridden configuration() method always returns the same object. It’s used in multiple places in JBehave. Sometimes it is passed on and sometimes, somebody get’s a fresh instance by calling your method again. Make sure that you don’t run into inconsistencies.

2) There are two main points to configure the behaviour of JBehave. The embedderControls that control how the embedder itself is working. And the configuration that adjusts everything else. The distinction is now always clear. I’ll explain that better in the next blog post.

And with this, it is time to go out, try it for yourself and write some tests. Let me know if this helped you to better understand how JBehave works. It sure helped me, when I wrote the blog post.