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 .
1package de.codecentric.simplejbehave._1_kiss;
2
3import org.jbehave.core.annotations.Given;
4import org.jbehave.core.annotations.Named;
5import org.jbehave.core.annotations.Then;
6import org.jbehave.core.annotations.When;
7import org.jbehave.core.steps.Steps;
8
9public class ExampleSteps extends Steps {
10 int x;
11
12 @Given("a variable x with value $value")
13 public void givenXValue(@Named("value") int value) {
14 x = value;
15 }
16
17 @When("I multiply x by $value")
18 public void whenImultiplyXBy(@Named("value") int value) {
19 x = x * value;
20 }
21
22 @Then("x should equal $value")
23 public void thenXshouldBe(@Named("value") int value) {
24 if (value != x)
25 throw new RuntimeException("x is " + x + ", but should be " + value);
26 }
27}
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:
1package de.codecentric.simplejbehave._1_kiss;
2
3import java.util.Arrays;
4import java.util.List;
5
6import org.jbehave.core.embedder.Embedder;
7
8public class SimpleJBehave {
9
10 private static Embedder embedder = new Embedder();
11 private static List<String> storyPaths = Arrays
12 .asList("de/codecentric/simplejbehave/Math.story");
13
14 public static void main(String[] args) {
15 embedder.candidateSteps().add(new ExampleSteps());
16 embedder.runStoriesAsPaths(storyPaths);
17 }
18}
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:
1Processing system properties {} 2Using controls EmbedderControls[batch=false,skip=false,generateViewAfterStories=true,ignoreFailureInStories=false,ignoreFailureInView=false,verboseFailures=false,verboseFiltering=false,storyTimeoutInSecs=300,threads=1] 3Running story de/codecentric/simplejbehave/Math.story 4Generating 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}' 5Reports view generated with 1 stories (of which 0 pending) containing 2 scenarios (of which 0 pending) 6Exception 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] 7 at org.jbehave.core.embedder.Embedder$ThrowingRunningStoriesFailed.handleFailures(Embedder.java:499) 8 at org.jbehave.core.embedder.Embedder.handleFailures(Embedder.java:265) 9 at org.jbehave.core.embedder.Embedder.generateReportsView(Embedder.java:252) 10 at org.jbehave.core.embedder.Embedder.generateReportsView(Embedder.java:233) 11 at org.jbehave.core.embedder.Embedder.runStoriesAsPaths(Embedder.java:212) 12 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.
1package de.codecentric.simplejbehave._2_junit;
2
3import java.util.Arrays;
4import java.util.List;
5
6import org.jbehave.core.junit.JUnitStories;
7
8public class SimpleJBehave extends JUnitStories {
9
10 public SimpleJBehave() {
11 super();
12 this.configuredEmbedder().candidateSteps().add(new ExampleSteps());
13 }
14
15 @Override
16 protected List<String> storyPaths() {
17 return Arrays.asList("de/codecentric/simplejbehave/Math.story");
18 }
19}
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:
1package de.codecentric.simplejbehave._3_stepsfactory;
2
3import org.jbehave.core.annotations.Given;
4import org.jbehave.core.annotations.Named;
5import org.jbehave.core.annotations.Then;
6import org.jbehave.core.annotations.When;
7
8public class ExampleSteps {
9 int x;
10
11 @Given("a variable x with value $value")
12 public void givenXValue(@Named("value") int value) {
13 x = value;
14 }
15
16 @When("I multiply x by $value")
17 public void whenImultiplyXBy(@Named("value") int value) {
18 x = x * value;
19 }
20
21 @Then("x should equal $value")
22 public void thenXshouldBe(@Named("value") int value) {
23 if (value != x)
24 throw new RuntimeException("x is " + x + ", but should be " + value);
25 }
26
27}
The class with the StepCandidates is now a true Plain Old Java Class. Yay 🙂 Next, let JBehave know how to create new steps.
1package de.codecentric.simplejbehave._3_stepsfactory;
2
3import java.util.Arrays;
4import java.util.List;
5
6import org.jbehave.core.junit.JUnitStories;
7import org.jbehave.core.steps.InjectableStepsFactory;
8import org.jbehave.core.steps.InstanceStepsFactory;
9import org.junit.runner.RunWith;
10
11import de.codecentric.jbehave.junit.monitoring.JUnitReportingRunner;
12
13@RunWith(JUnitReportingRunner.class)
14public class SimpleJBehave extends JUnitStories {
15
16 public SimpleJBehave() {
17 super();
18 }
19
20 @Override
21 public InjectableStepsFactory stepsFactory() {
22 return new InstanceStepsFactory(configuration(), new ExampleSteps());
23 }
24
25 @Override
26 protected List<String> storyPaths() {
27 return Arrays.asList("de/codecentric/simplejbehave/Math.story");
28 }
29}
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 🙂
1package de.codecentric.simplejbehave._4_configuration; 2 3import java.util.Arrays; 4import java.util.List; 5import java.util.Locale; 6 7import org.jbehave... 8 9import org.junit.runner.RunWith; 10 11import com.thoughtworks.paranamer.NullParanamer; 12 13import de.codecentric.jbehave.junit.monitoring.JUnitReportingRunner; 14 15@RunWith(JUnitReportingRunner.class) 16public class SimpleJBehave extends JUnitStories { 17 18 private Configuration configuration; 19 20 public SimpleJBehave() { 21 super(); 22 configuration = new Configuration() { 23 }; 24 25 // configuration.doDryRun(false); "no dry run" is implicit by using 26 // default StoryControls 27 28 // configuration.useDefaultStoryReporter(new ConsoleOutput()); 29 // deprecated -- rather use StoryReportBuilder 30 31 configuration.useFailureStrategy(new RethrowingFailure()); 32 configuration.useKeywords(new LocalizedKeywords(Locale.ENGLISH)); 33 configuration.usePathCalculator(new AbsolutePathCalculator()); 34 configuration.useParameterControls(new ParameterControls()); 35 configuration.useParameterConverters(new ParameterConverters()); 36 configuration.useParanamer(new NullParanamer()); 37 configuration.usePendingStepStrategy(new PassingUponPendingStep()); 38 configuration.useStepCollector(new MarkUnmatchedStepsAsPending()); 39 configuration.useStepdocReporter(new PrintStreamStepdocReporter()); 40 configuration.useStepFinder(new StepFinder()); 41 configuration.useStepMonitor(new SilentStepMonitor()); 42 configuration 43 .useStepPatternParser(new RegexPrefixCapturingPatternParser()); 44 configuration.useStoryControls(new StoryControls()); 45 configuration.useStoryLoader(new LoadFromClasspath()); 46 configuration.useStoryParser(new RegexStoryParser(configuration 47 .keywords())); 48 configuration.useStoryPathResolver(new UnderscoredCamelCaseResolver()); 49 configuration.useStoryReporterBuilder(new StoryReporterBuilder()); 50 configuration.useViewGenerator(new FreemarkerViewGenerator()); 51 52 EmbedderControls embedderControls = configuredEmbedder() 53 .embedderControls(); 54 embedderControls.doBatch(false); 55 embedderControls.doGenerateViewAfterStories(true); 56 embedderControls.doIgnoreFailureInStories(false); 57 embedderControls.doIgnoreFailureInView(false); 58 embedderControls.doSkip(false); 59 embedderControls.doVerboseFailures(false); 60 embedderControls.doVerboseFiltering(false); 61 embedderControls.useStoryTimeoutInSecs(300); 62 embedderControls.useThreads(1); 63 } 64 65 @Override 66 public Configuration configuration() { 67 return configuration; 68 } 69 70 @Override 71 public InjectableStepsFactory stepsFactory() { 72 return new InstanceStepsFactory(configuration(), new ExampleSteps()); 73 } 74 75 @Override 76 protected List<String> storyPaths() { 77 return Arrays.asList("de/codecentric/simplejbehave/Math.story"); 78 } 79}
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.
More articles
fromAndreas Ebbert-Karroum
Your job at codecentric?
Jobs
Agile Developer und Consultant (w/d/m)
Alle Standorte
More articles in this subject area
Discover exciting further topics and let the codecentric world inspire you.
Gemeinsam bessere Projekte umsetzen.
Wir helfen deinem Unternehmen.
Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.
Hilf uns, noch besser zu werden.
Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.
Blog author
Andreas Ebbert-Karroum
Agile Principal Consultant
Do you still have questions? Just send me a message.
Do you still have questions? Just send me a message.