Introduction

Dependency Injection has become an integral part of any modern software design. We discuss here the different ways in which JBehave supports dependency injection.

JBehave supports the following dependency injection containers:

  • Guice (when the extension module jbehave-guice is added to the classpath),
  • Needle (when the extension module jbehave-needle is added to the classpath),
  • PicoContainer (when the extension module jbehave-pico is added to the classpath),
  • Spring (when the extension module jbehave-spring is added to the classpath),
  • Weld (when the extension module jbehave-weld is added to the classpath).

Fully-working examples of running stories with dependency-injection support can be found in the trader-[guice|needle|pico|spring|weld] examples. Note that dependency injection examples only concentrate on the composition via the respective containers but do not replicate what is in the trader example. Indeed, they use the same stories and steps instances, simply configured in a different way.

Embedder class-level injection

Using Guice

When using Guice we can specify the inject from separate modules for configuration and steps.

@RunWith(GuiceAnnotatedEmbedderRunner.class)
@Configure()
@UsingEmbedder(embedder = Embedder.class, generateViewAfterStories = true, ignoreFailureInStories = true, ignoreFailureInView = true)
@UsingGuice(modules = { ConfigurationModule.class, StepsModule.class })
public class AnnotatedEmbedderUsingGuice extends InjectableEmbedder {
 
    @Test
    public void run() {
        injectedEmbedder().runStoriesAsPaths(storyPaths());
    }
 
    protected List<String> storyPaths() {
        String searchInDirectory = codeLocationFromPath("../trader/src/main/java").getFile();
        return new StoryFinder().findPaths(searchInDirectory, asList("**/*.story"), null);
    }
 
    // Guice modules
    public static class ConfigurationModule extends AbstractModule {
 
        @Override
        protected void configure() {
            bind(StepPatternParser.class).toInstance(new RegexPrefixCapturingPatternParser("%"));
            bind(StoryLoader.class).toInstance(new LoadFromClasspath(this.getClass().getClassLoader()));
            bind(ParameterConverter.class).toInstance(new DateConverter(new SimpleDateFormat("yyyy-MM-dd")));
            bind(StoryReporterBuilder.class).toInstance(
                    new StoryReporterBuilder().withDefaultFormats().withFormats(CONSOLE, HTML, TXT, XML)
                            .withCodeLocation(CodeLocations.codeLocationFromClass(this.getClass())).withFailureTrace(
                                    true));
        }
 
    }
 
    public static class StepsModule extends AbstractModule {
 
        @Override
        protected void configure() {
            bind(TradingService.class).in(Scopes.SINGLETON);
            bind(GuiceTraderSteps.class).in(Scopes.SINGLETON);
            bind(BeforeAfterSteps.class).in(Scopes.SINGLETON);
            bind(AndSteps.class).in(Scopes.SINGLETON);
            bind(CalendarSteps.class).in(Scopes.SINGLETON);
            bind(PriorityMatchingSteps.class).in(Scopes.SINGLETON);
            bind(SandpitSteps.class).in(Scopes.SINGLETON);
            bind(SearchSteps.class).in(Scopes.SINGLETON);
        }
 
    }
 
}

Using Needle

When using Needle, we can specify the steps used in tests.

@RunWith(NeedleAnnotatedEmbedderRunner.class)
@Configure()
@UsingSteps(instances = Steps.class)
@UsingEmbedder()
@UsingNeedle
public class RunningWithAnnotatedEmbedderRunner extends InjectableEmbedder {
 
    @Test
    public void run() {
        injectedEmbedder().runStoriesAsPaths(storyPaths());
    }
 
    protected List<String> storyPaths() {
        String searchInDirectory = codeLocationFromPath("../trader/src/main/java").getFile();
        return new StoryFinder().findPaths(searchInDirectory, asList("**/*.story"), null);
    }
 
    public static class Steps {
        @NeedleInjectionProvider
        InjectionProvider<ValueGetter> provider = new ValueGetterProvider();
 
        @Inject
        private ValueGetter getter;
 
        @Given("Some")
        public boolean complete() {
            return ValueGetter.VALUE.equals(getter.getValue());
        }
    }
}

Using PicoContainer

When using PicoContainer we can specify the container from separate modules for configuration and steps.

@RunWith(PicoAnnotatedEmbedderRunner.class)
@Configure()
@UsingEmbedder(embedder = Embedder.class, generateViewAfterStories = true, ignoreFailureInStories = true, ignoreFailureInView = true)
@UsingPico(modules = { ConfigurationModule.class, StepsModule.class })
public class AnnotatedEmbedderUsingPico extends InjectableEmbedder {
 
    @Test
    public void run() {
        injectedEmbedder().runStoriesAsPaths(storyPaths());
    }
 
    protected List<String> storyPaths() {
        String searchInDirectory = codeLocationFromPath("../trader/src/main/java").getFile();
        return new StoryFinder().findPaths(searchInDirectory, asList("**/*.story"), null);
    }
 
    public static class ConfigurationModule implements PicoModule {
 
        public void configure(MutablePicoContainer container) {
            container.addComponent(StepPatternParser.class, new RegexPrefixCapturingPatternParser("%"));
            container.addComponent(StoryLoader.class, new LoadFromClasspath(this.getClass().getClassLoader()));
            container.addComponent(ParameterConverter.class, new DateConverter(new SimpleDateFormat("yyyy-MM-dd")));
            container.addComponent(new StoryReporterBuilder().withDefaultFormats().withFormats(CONSOLE, HTML, TXT, XML)
                    .withCodeLocation(CodeLocations.codeLocationFromClass(this.getClass())).withFailureTrace(true));
        }
 
    }
 
    public static class StepsModule implements PicoModule {
 
        public void configure(MutablePicoContainer container) {
            container.addComponent(TradingService.class);
            container.addComponent(TraderSteps.class);
            container.addComponent(BeforeAfterSteps.class);
            container.addComponent(AndSteps.class);
            container.addComponent(CalendarSteps.class);
            container.addComponent(PriorityMatchingSteps.class);
            container.addComponent(SandpitSteps.class);
            container.addComponent(SearchSteps.class);
        }
 
    }
 
}

Using Spring

When using Spring, we can specify the context from separate locations for configuration and steps.

@RunWith(SpringAnnotatedEmbedderRunner.class)
@Configure()
@UsingEmbedder(embedder = Embedder.class, generateViewAfterStories = true, ignoreFailureInStories = true, ignoreFailureInView = true)
@UsingSpring(resources = { "org/jbehave/examples/trader/spring/configuration.xml",
        "org/jbehave/examples/trader/spring/steps.xml" })
public class AnnotatedEmbedderUsingSpring extends InjectableEmbedder {
 
    @Test
    public void run() {
        injectedEmbedder().runStoriesAsPaths(storyPaths());
    }
 
    protected List<String> storyPaths() {
        String searchInDirectory = codeLocationFromPath("../trader/src/main/java").getFile();
        return new StoryFinder().findPaths(searchInDirectory, asList("**/*.story"), null);
    }
 
}

Using Weld

When using Weld you can inject configuration and steps using the standard Context and Dependency Injection (CDI) annotations

@RunWith(WeldAnnotatedEmbedderRunner.class)
@Configure()
@UsingEmbedder(embedder = Embedder.class, generateViewAfterStories = true, ignoreFailureInStories = true, ignoreFailureInView = true)
@UsingWeld
public class AnnotatedEmbedderUsingWeld extends InjectableEmbedder {
 
    @Test
    public void run() {
        injectedEmbedder().runStoriesAsPaths(storyPaths());
    }
 
    protected List<String> storyPaths() {
        return new StoryFinder().findPaths(codeLocationFromPath("../trader/src/main/java"), "**/*.story", "");
    }
}

To override the JBehave configuration you write a producer method and annotate the method with an @WeldConfiguration

@ApplicationScoped
public class ConfigurationProducer {
     
    public ConfigurationProducer() {}
 
    @Produces @WeldConfiguration
    public Configuration getConfiguration() {
        return new MostUsefulConfiguration()
            .useStoryControls(new StoryControls()
                .doDryRun(false)
                .doSkipScenariosAfterFailure(false))
            .useStepPatternParser(new RegexPrefixCapturingPatternParser("%"))
            .useStoryLoader(new LoadFromClasspath(getClass().getClassLoader()))
            .useStoryReporterBuilder(new StoryReporterBuilder()
                .withDefaultFormats()
                .withFormats(CONSOLE, HTML, TXT, XML))
            .useParameterConverters(new ParameterConverters()
                .addConverters(new DateConverter(new SimpleDateFormat("yyyy-MM-dd"))));
    }
}

Steps class-level injection

Steps classes often use external dependencies to interface to the system whose behaviour is being verified.

Using Guice

CandidateSteps can be created with Guice using the GuiceStepsFactory:

@Override
public InjectableStepsFactory stepsFactory() {
    return new GuiceStepsFactory(configuration(), createInjector());
}
 
private Injector createInjector() {
    return Guice.createInjector(new StepsModule());
}

where the StepsModule defines the steps classes and their scope:

public static class StepsModule extends AbstractModule {
 
    @Override
    protected void configure() {
        bind(TradingService.class).in(Scopes.SINGLETON);
        bind(GuiceTraderSteps.class).in(Scopes.SINGLETON);
        bind(BeforeAfterSteps.class).in(Scopes.SINGLETON);
        bind(AndSteps.class).in(Scopes.SINGLETON);
        bind(CalendarSteps.class).in(Scopes.SINGLETON);
        bind(PendingSteps.class).in(Scopes.SINGLETON);
        bind(PriorityMatchingSteps.class).in(Scopes.SINGLETON);
        bind(SandpitSteps.class).in(Scopes.SINGLETON);
        bind(SearchSteps.class).in(Scopes.SINGLETON);
    }
 
}

and the steps classes requiring injection are appropriately Guice-annotated:

public class GuiceTraderSteps extends TraderSteps {
 
    @Inject
    public GuiceTraderSteps(TradingService service) {
        super(service);
    }
 
}

Using Needle

CandidateSteps can be created with Needle using the NeedleStepsFactory:

@Override
public InjectableStepsFactory stepsFactory() {
    final Class<?>[] steps = new Class<?>[] { MySteps.class };
 
    final Set<InjectionProvider<?>> providers = new HashSet<InjectionProvider<?>>();
    // always inject the same instance
    providers.add(DefaultInstanceInjectionProvider.providerFor(...));
    return new NeedleStepsFactory(configuration(), providers, steps);
}   

Steps classes can use injection points which are by default supplied with a mock implementation provided by Needle's mock provider (defaults to mockito, can be changed to easymock). This behaviour may be changed and an Injection provider for a given inhection point can be configured, so a specific instance can be supplied. See Needle documentation for more details.

public class MySteps {
 
    @Inject
    private TradingService tradingService;
     
     
    @Given("I make trade")
    public void doNothing() {
        tradingService.newStock(....);
    }
    ......
 
}

Using PicoContainer

CandidateSteps can be created with PicoContainer using the PicoStepsFactory:

@Override
public InjectableStepsFactory stepsFactory() {
    return new PicoStepsFactory(configuration(), createPicoContainer());
}
 
private PicoContainer createPicoContainer() {
    MutablePicoContainer container = new DefaultPicoContainer(new Caching().wrap(new ConstructorInjection()));
    container.addComponent(TradingService.class);
    container.addComponent(TraderSteps.class);
    container.addComponent(BeforeAfterSteps.class);
    container.addComponent(AndSteps.class);
    container.addComponent(CalendarSteps.class);
    container.addComponent(PendingSteps.class);
    container.addComponent(PriorityMatchingSteps.class);
    container.addComponent(SandpitSteps.class);
    container.addComponent(SearchSteps.class);
    return container;
}

Using Spring

CandidateSteps can be created with Spring using the SpringStepsFactory:

@Override
public InjectableStepsFactory stepsFactory() {
    return new SpringStepsFactory(configuration(), createContext());
}
 
private ApplicationContext createContext() {
    return new SpringApplicationContextFactory("org/jbehave/examples/trader/spring/steps.xml")
            .createApplicationContext();
}

Using Weld

CandidateSteps can be created by annotating a class using the @WeldStep annotation WeldStep:

private static WeldContainer container;
 
static {
    container = new WeldBootstrap().initialize();
}
 
@Override
public InjectableStepsFactory stepsFactory() {
    return container.instance().select(WeldStepsFactory.class).get();
}

where the steps class is appropriately Weld-annotated:

@WeldStep
public class MyTradingSteps {
 
    @Inject
    private TradingService tradingService;
     
    @Given("I make trade")
    public void doNothing() {
        tradingService.newStock(....);
    }
    ......
}