Principal Architect, Pearson Digital Learning Ryan is a DZone MVB and is not an employee of DZone and has posted 2 posts at DZone. You can read more from them at their website. View Full User Profile

How to Scope Scenarios with JBehave and Guice

06.05.2013
| 3627 views |
  • submit to reddit
If you use JBehave with a dependency injection framework such as Spring or Guice, you may quickly realize that scoping the Step classes gets a little tricky.  By default, both of those frameworks provide two basic scopes: instance (or prototype) scope, and singleton scope. 

Neither one of these options is great for use with JBehave.  Instance scope is a problem because JBehave then creates a new Step class for every step in a scenario, making it impossible to share state between steps without using static (global) state.  Singleton scope is a different side of the same problem: state ends up shared among all scenarios.  In either case, to make things work you must remember to clean up the global state after each scenario.

A simpler solution would be to implement a custom "scenario" scope.  I will show you how to do this for Guice below.

First, we need to define a new custom scope by implementing Guice'sScope interface.  This class will be a container that adds and manages our dependencies when the scope is entered, and removes them when the scope is exited.  This could be potentially daunting and error prone, but fortunately the Guice developers have provided us with a default calledSimpleScope, which does all this for us.  This class is sufficient for our needs, and you can copy it as-is straight into your source code.

Second, we need to tell JBehave when to actually create a new scope, and when to close the scope out.  Since we want to scope our dependencies to scenarios, we use JBehave's @BeforeScenario and @AfterScenario annotations to enter and exit each scope.  Note that we must inject our copy of SimpleScope, which is what actually manages the scoped dependencies.

 public class ScenarioContext { 




private SimpleScope scope; 




@Inject 
public ScenarioContext( @Named ( "scenarioScope" ) SimpleScope scope ) { 
this.scope = scope;
} 




@BeforeScenario 
public void beforeScenario() { 
scope.enter(); 
} 




@AfterScenario 
public void afterScenario() { 
scope.exit(); 
} 
} 

Third, much like the Singleton annotation, we need a binding annotation to inform Guice about how we'd like our step classes scoped.  We will use this new annotation to bind instances to our new SimpleScope class.  We create it simply like thus:

 import static java.lang.annotation.ElementType.METHOD; 
import static java.lang.annotation.ElementType.TYPE; 
import static java.lang.annotation.RetentionPolicy.RUNTIME; 




import java.lang.annotation.Retention; 
import java.lang.annotation.Target; 




import com.google.inject.ScopeAnnotation; 




@Target ( { TYPE, METHOD } ) 
@Retention ( RUNTIME ) 
@ScopeAnnotation 
public @interface ScenarioScope {} 

There are two parts to the final step.  The first is to actually bind our step classes to our new scope, which is accomplished simply by providing the class file to the binder using .in().  However, we also need to inform Guice about how to manage the SimpleScope container.

 public class AppModule extends AbstractModule { 
@Override 
protected void configure() { 
setUpScenarioScope(); 




bind( MySteps.class ).in( ScenarioScope.class ); 
} 




private void setUpScenarioScope() { 




bindScope( ScenarioScope.class, scenarioScope ); 




SimpleScope scenarioScope = new SimpleScope(); 
bind( SimpleScope.class ).annotatedWith( Names.named( "scenarioScope" ) ).toInstance( scenarioScope ); 
bind( ScenarioContext.class ).in( Singleton.class ); 
} 
} 

The setUpScenarioScope() method above does a couple of things:
  • informs Guice of our new scope, using bindScope()
  • creates an instance of our SimpleScope class for managing dependencies (we only need one)
  • ensures that instance can be injected into our JBehave-annotated context class
  • binds that context in the singleton scope
That's it!  All steps annotated for scenario scope will be able to share data within a single step class, while  guaranteeing a fresh set of steps for every new scenario.

Known issue: This approach is not currently compatible with jbehave-junit-runner library.  That library creates a special JBehave runner which formats the test results in a standard JUnit output, and it relies on a older copy of JBehave that causes a chicken-and-egg problem with Step creation.  A patch has been submitted to fix this, but to date it has not been incorporated into a release.  A workaround is to build from source and apply the patch manually, and make sure you are using JBehave 3.8+.
Published at DZone with permission of Ryan Nelson, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)