SpecFlow Context Injection - Service Injection

Hackered
Monday, March 3, 2014
by Sean McAlinden

Context Injection is one of the lesser known/used features of SpecFlow however I believe it is a great part of the framework which contributes to keeping your test code clean.   As it is essentially dependency injection you can use it to inject services / controllers as you would using any other container such as Castle Windsor, Spring, Ninject etc., however you can also use it to pass around state objects bound to the scenario context.   Lets take a look at the first usage: service injection.   If we were building a set of SpecFlow steps for a REST API, we may be using controllers to perform the actual http requests, possibly using a library such as RestSharp.   We could instantiate a controller in each of our tests  by newing it up:  

var customerController = new CustomerController();

Whilst this is fine, it adds unnecessary code noise, same as it would in development code generally.   With context injection we can simply use constructor injection which is a much cleaner pattern for these types of class invariants:  

[Binding]
public class CreateCustomerSteps
{
    private CustomerController customerController;

    public CreateCustomerSteps(CustomerController customerController)
    {
        this.customerController = customerController;
    }

    [Given(@"I create a new customer")]
    public void GivenICreatedANewCustomer()
    {
        customerController.Create(new Customer());
    }
}

The magic bit: As the class is decorated with a [Binding] attribute, it will automatically use the context injection container so there is no setup. If the CustomerController hasn't been instantiated previously, the container will instantiate it automatically so again there is nothing for use to do other than just use the controller.   Notes: bear in mind the context injection lifetime is bound to the scenario context so ideally in the case of service injection be careful if your service/controller maintains any state as it will be shared throughout the steps that use it within the scenario context. I would suggest the best thing to do is avoid maintaining state in the service class to avoid any unwanted side effects.  

Using Interfaces Instead of Concrete Types

If you require more generic steps where the services could be of different concrete types, you can also pass interfaces as arguments and register the concrete types within the Object Container using the following syntax:

[Binding]
public class RegisterConcreteTypeExample
{
    public RegisterConcreteTypeExample(IObjectContainer objectContainer)
    {
        var myClass = new MyClass();
        objectContainer.RegisterInstanceAs<IMyInterface>(myClass);
    }
}

[Binding]
public class StepsWithInterfaceArgument
{
    private readonly IMyInterface iMyInterface;

    public StepsWithInterfaceArgument(IMyInterface iMyInterface)
    {
        this.iMyInterface = iMyInterface;
    }
}