Story writers often find themselves repeating scenarios, or parts thereof, by simply changing some parameter values. These are ideal candidates for using JBehave's parametrisation features. Let's look at the example:
Given a stock of symbol STK1 and a threshold of 10.0 When the stock is traded at 5.0 Then the alert status should be OFF When the stock is traded at 11.0 Then the alert status should be ON |
We notice that two lines are repeated and identical but for the values. We can then rewrite this scenario as:
Given a stock of <symbol> and a <threshold> When the stock is traded at <price> Then the alert status should be <status> Examples : |symbol|threshold|price|status| |STK1 |10.0 |5.0 |OFF | |STK1 |10.0 |11.0 |ON | |
The Examples: keyword signals that the entire scenario is parametrised and should be repeated for as many times as there are data rows in the examples table. At each execution, the named parameters are taken from the corresponding row.
Also it's possible to put the Examples to the story level:
Lifecycle: Examples : |symbol|threshold|price|status| |STK1 |10.0 |5.0 |OFF | |STK1 |10.0 |11.0 |ON | Scenario : Configure stock Given a stock of <symbol> and a <threshold> When the stock is traded at <price> Then the alert status should be <status> |
In this case the Examples section in the Lifecycle indicates that the entire story is parametrised and all scenarios should be repeated as many times as there are data rows in the examples table.
It is allowed to use both story level and scenario level Examples at the same time. The story level rows are merged with scenario level ones if present. In case of conflicting keys, the values from the scenario level take precedence. For example, the following story consists of the 4 scenario iterations with different datasets:
Lifecycle: Examples : |symbol| |STK1 | |STK2 | Scenario : Configure stock Given a stock of <symbol> and a <threshold> When the stock is traded at <price> Then the alert status should be <status> Examples : |threshold|price|status| |10.0 |5.0 |OFF | |10.0 |11.0 |ON | |
One important difference to underline in using table examples is that they require named parameters for the step candidates to be matched to Java methods. The named parameters allow the parameters to be injected using the table row values with the corresponding header name, instead of being extracted from the annotation pattern match. As such, the step annotation pattern must hold the verbatim textual step, e.g.:
@Given ( "a stock of <symbol> and a <threshold>" ) public void stock( @Named ( "symbol" ) String symbol, @Named ( "threshold" ) double threshold) { // ... } |
Also note that while the characters delimiting the parameter names in the regex pattern are purely conventional - they only serve the purpose of matching the step method in a readable manner - the use of the angle brackets is required as it is used to replace the name with the value in the reporting.
It is also important to note that the same (@Named
-annotated)
methods can match steps that are executed both as standalone or via
examples table, provided that both regex patterns are configured, one as
the main pattern and one as an alias:
@Given ( "a stock of symbol $symbol and a threshold of $threshold" ) // standalone @Alias ( "a stock of <symbol> and a <threshold>" ) // examples table public void stock( @Named ( "symbol" ) String symbol, @Named ( "threshold" ) double threshold) { // ... } |
Moreover, the examples table alias can happily co-exists with other standalone aliases:
@Given ( "a stock of symbol $symbol and a threshold of $threshold" ) // standalone @Aliases (values={ "a stock with a symbol of $symbol and a threshold of $threshold" , // a standalone alias "a stock of <symbol> and a <threshold>" }) // an examples table alias public void stock( @Named ( "symbol" ) String symbol, @Named ( "threshold" ) double threshold) { // ... } |
The parameter name delimiters are purely conventional. The step matching works just as well if other delimiters - e.g. [] - are used, provided the patterns are updated accordingly:
Given a stock of [symbol] and a [threshold] When the stock is traded at [price] Then the alert status should be [status] Examples : |symbol|threshold|price|status| |STK1|10.0|5.0|OFF| |STK1|10.0|11.0|ON| |
@Given ( "a stock of [symbol] and a [threshold]" ) public void stock( @Named ( "symbol" ) String symbol, @Named ( "threshold" ) double threshold) { // ... } |
The only thing we need to do is to tell the StepCreator to use the custom parameter name delimiters when replacing the parameter value. We do this by configuring a custom instance of ParameterControls:
new MostUsefulConfiguration() .useParameterControls( new ParameterControls().useNameDelimiterLeft( "[" ).useNameDelimiterRight( "]" )); |
An alternative way to annotations in specifying the named parameters, is to leverage the name delimiters themselves. Going back to our example:
Given a stock of <symbol> and a <threshold> When the stock is traded at <price> Then the alert status should be <status> Examples : |symbol|threshold|price|status| |STK1|10.0|5.0|OFF| |STK1|10.0|11.0|ON| |
We can configure JBehave to interpret the name contained between the delimiters as the parameter name and look it up in the parameters provided by the examples table. The default behaviour of parameter lookup is overridden via the ParameterControls:
new MostUsefulConfiguration() .useParameterControls( new ParameterControls().useDelimiterNamedParameters( true )); |
In this mode, the step method would look much simplified:
@Given ( "a stock of $symbol and a $threshold" ) public void stock(String symbol, double threshold) { // ... } |
Another use case of name delimited parameters is to reuse the same step for different parameter values (with different names), e.g.:
Given a stock of <symbol> and a <threshold> And a stock of <alternate_symbol> and a <threshold> Examples : |symbol|alternate_symbol|threshold| |STK1|ALT1|1.0| |
Both steps would be matched by the method:
@Given ( "a stock of symbol $symbol" ) public void stock(String symbol) { // ... } |
The parameters table can also be loaded from an external resource, be it a classpath resource or a URL.
Given a stock of <symbol> and a <threshold> When the stock is traded at <price> Then the alert status should be <status> Examples : org/jbehave/examples/trader/stories/trades.table |
We need to enable the parser to find the resource with the appropriate resource loader configured via the ExamplesTableFactory:
new MostUsefulConfiguration() .useStoryParser( new RegexStoryParser( new ExamplesTableFactory( new LoadFromClasspath( this .getClass())))) |