Configuring Stories

In JBehave, there is a separation of concerns between RunnableStory and Steps classes:

  • RunnableStory classes are responsible for the configuration of story execution: there can be a one-to-one or one-to-may mapping between executable RunnableStory classes and textual story files, using JUnitStory or JUnitStories respectively.
  • Steps classes contain the Java methods that match the textual steps in the stories: which Steps classes are used when executing a story is configured via the RunnableStory class.

Even if each executable RunnableStory Java class can be configured independently, it is good practice to collect the configuration that applies to all stories in an abstract (i.e. not executable) base class:

public abstract class TraderStory extends JUnitStory {

    public TraderStory(final Class storyClass) {
          // start with default story configuration, overriding story definer and reporter
          StoryConfiguration storyConfiguration = new MostUsefulStoryConfiguration();
          storyConfiguration.useStoryPathResolver(new UnderscoredCamelCaseResolver(".story"));
          String storyPath = storyConfiguration.storyPathResolver().resolve(storyClass);
          storyConfiguration.useStoryReporter(new StoryReporterBuilder(new FilePrintStreamFactory(storyPath))
                  .with(CONSOLE)
                  .with(TXT)
                  .with(HTML)
                  .with(XML)
                  .build());
          useConfiguration(storyConfiguration);

          // start with default steps configuration, overriding parameter converters, pattern builder and monitor
          StepsConfiguration stepsConfiguration = new MostUsefulStepsConfiguration();
          StepMonitor monitor = new SilentStepMonitor();
          stepsConfiguration.useParameterConverters(new ParameterConverters(
                monitor, new DateConverter("dd/MM/yyyy")));  // define converter for Dates with custom pattern
          stepsConfiguration.usePatternBuilder(new PrefixCapturingPatternBuilder("%")); // use '%' instead of '$' to identify parameters
          stepsConfiguration.useMonitor(monitor);
          addSteps(createSteps(stepsConfiguration));
      }

      protected CandidateSteps[] createSteps(StepsConfiguration configuration) {
          return new StepsFactory(configuration).createCandidateSteps(new TraderSteps(new TradingService()), new BeforeAfterSteps());
      }

    }  

To run multiple stories, specified as story paths, via a single RunnableStory instance:

public class TraderStories extends JUnitStories {

    public TraderStories() {
        // start with default story configuration, overriding story definer and reporter
        StoryConfiguration storyConfiguration = new MostUsefulStoryConfiguration();
        storyConfiguration.useStoryPathResolver(new UnderscoredCamelCaseResolver(".story"));
        // Using URLs to define stories
        storyConfiguration.useStoryLoader(new LoadFromURL());
        for ( String storyPath : storyPaths() ){
            StoryReporter storyReporter = new StoryReporterBuilder(new FilePrintStreamFactory(storyPath))
                            .with(CONSOLE)
                            .with(TXT)
                            .with(HTML)
                            .with(XML)
                            .build();
            storyConfiguration.addStoryReporter(storyPath, storyReporter);

        }
        useConfiguration(storyConfiguration);

        // start with default steps configuration, overriding parameter converters, pattern builder and monitor
        StepsConfiguration stepsConfiguration = new MostUsefulStepsConfiguration();
        StepMonitor monitor = new SilentStepMonitor();
        stepsConfiguration.useParameterConverters(new ParameterConverters(
                monitor, new DateConverter("dd/MM/yyyy")));  // define converter for Dates with custom pattern
        stepsConfiguration.usePatternBuilder(new PrefixCapturingPatternBuilder("%")); // use '%' instead of '$' to identify parameters
        stepsConfiguration.useMonitor(monitor);
        addSteps(createSteps(stepsConfiguration));
    }

    protected CandidateSteps[] createSteps(StepsConfiguration configuration) {
        return new StepsFactory(configuration).createCandidateSteps(new TraderSteps(new TradingService()), new BeforeAfterSteps());
    }

    @Override
    protected List storyPaths() {
        // Defining story paths via URLs
        return asList(storyURL("trader_is_alerted_of_status.story"),
                      storyURL("wildcard_search.story"));
    }

    private String storyURL(String name){
        String codeLocation = new StoryLocation("", this.getClass()).getCodeLocation();
        String urlPattern = "file://"+ codeLocation +"org/jbehave/examples/trader/stories/{0}";
        return MessageFormat.format(urlPattern, name);

    }
Note that in this second example, we are using story paths as URLs, and correspondingly we configure the use of LoadFromURL.

If we wanted to use an inversion of control container to compose our Steps with all its dependencies, all we'd need to do is to override the createSteps method using a different StepsFactory and use that as your base Story class. For example, to use with a Spring container:

public abstract class SpringTraderStory extends TraderStory {

    public SpringTraderStory(Class storyClass) {
        super(storyClass);
    }

    @Override
    protected CandidateSteps[] createSteps(StepsConfiguration configuration) {
        ListableBeanFactory parent = new SpringApplicationContextFactory("org/jbehave/examples/trader/spring/steps.xml")
                                    .getApplicationContext();
        return new SpringStepsFactory(configuration, parent).createCandidateSteps();
    }

}

Once we have a base RunnableStory class, all we need to do is to extend it providing the name of the executable Story class that maps to the textual story file. For example, to map to trader_is_alerted_of_status.story using the resolver defined above:

public class TraderIsAlertedOfStatus extends TraderStory {

    public TraderIsAlertedOfStatus() {
        super(TraderIsAlertedOfStatus.class);
    }

}
Note that JBehave follow the configuration-by-convention approach, by which a default value of the configuration element is provided and can be overridden, if so desired.

Story Configuration Elements

While Stories are designed to be highly configurable, they come with a default behaviour for the most useful configuration. Configuration is the main interface for configuring all the components of a story.

The configurable elements of the story include:

StepCreator: Represents the strategy for the creation of executable steps from a given story definition matching a list of candidate steps. The default implementation is MarkUnmatchedStepsAsPending.

StoryParser: Parses stories contained in a textual story file. The default implementation is RegexStoryParser.

StoryReporter: Allows the runner to report the state of running stories. The default implementation is PrintStreamOutput.

ErrorStrategy: Allows to define the strategy for error handling. The default value is RETHROW.

Keywords: Allows to specify the keywords used. The default value is LocalizedKeywords.

JBehave provides two useful base implementations that users can extend to override only the elements that differ from default behaviour:

MostUsefulStoryConfiguration: provides a default configuration that most users will find appropriate

PropertyBasedStoryConfiguration: overrides way to configure via system properties

Steps Configuration Elements

Steps can also be configured to a high degree via the StepsConfiguration. Among the elements that can be configured are:

StepPatternBuilder: defaults to PrefixCapturingPatternBuilder.

StepMonitor: defaults to SilentStepMonitor, useful to either debug the step matching or to describe the steps being performed to some output.

ParameterConverters: facade for collecting user-defined ParameterConverters.

Keywords: defaults to LocalizedKeywords.