SpecFlow Context Injection - State Injection

Hackered
Monday, March 3, 2014
by Sean McAlinden

Following on from my previous post about service injection, state can also be passed between step definitions using constructor injection.   A simple example would be a Customer model used within a CreateCustomer step definition file and an AddOrder step definition file: these classes could look something like the following:  

Customer Model:

public class Customer
{
    public Guid Id { get; set; }
}

Create Customer Steps:

[Binding]
public class CreateCustomerSteps
{
    private readonly Customer customer;

    public CreateCustomerSteps(Customer customer)
    {
        this.customer = customer;
    }

    [Given(@"I have created a new customer")]
    public void GivenIHaveCreatedANewCustomer()
    {
        // this would be a call to a service or a selenium script
        customer.Id = "123";
    }
}

Add Order Steps:

[Binding]
public class AddOrderSteps
{
    private readonly Customer customer;

    public AddOrderSteps(Customer customer)
    {
        this.customer = customer;
    }

    [When(@"I add a new order")]
    public void WhenIAddANewOrder()
    {
        var id = customer.Id; // equals "123"
        // Call add order service with the customer id.
    }
}

So this is what is happening:  
1. A Customer object is injected into the CreateCustomerSteps. This automatically instantiates a new Customer object for us.
2. We update the Id property to "123"
3. We inject the Customer object into the AddOrderSteps
4. We use the customer Id property to create an order, and the Id property is already set to "123" as it has was set in the CreateCustomerSteps.

If step 1 feels a little strange having the container instantiate the new Customer object, you could also add it to the container manually within a step using the following code:

[Binding]
public class CreateCustomerSteps
{
    private readonly IObjectContainer objectContainer;

    public CreateCustomerSteps(IObjectContainer objectContainer)
    {
        this.objectContainer = objectContainer;
    }

    [Given(@"I have created a new customer")]
    public void GivenIHaveCreatedANewCustomer()
    {
        var customer = new Customer { Id = "123" };
        objectContainer.RegisterInstanceAs(customer);
    }
}

I really like the context injection aspect to SpecFlow and I'm sure I'll be writing some more posts in the future to show off some other cool ways it can be utilised to keep our test code clean.