Strongly Typed ScenarioContext in Specflow

I write a lot of BDD tests day to day using SpecFlow.  I really love SpecFlow as a tool, as it allows you to write clear acceptance tests that are driven by business criteria and written in a language that the business can understand.

One of the things I dislike about SpecFlow however is the way the ScenarioContext is implemented.  The ScenarioContext is a dictionary that is passed to each step in your test by the SpecFlow framework.  It is a place where you can store test data between steps.  The trouble is the signature to get and set an item is as follows:

// Set a value
ScenarioContext.Current.Add(string key, object value);

// Get a value
var value = ScenarioContext[string key];

The trouble with this is that when people are not disciplined you end up with magic strings everywhere, scattered throughout all of your step definition files.  You also lose your compiler as a safety guard.  For example:


[Binding]
public class Steps
{
    [Given("I have done something")]
    public GivenIHaveDoneSomething()
    {
        ScenarioContext.Add("customerId", 1234);
    }

    // then further down
    [When("I do this action")]
    public WhenIDoThisAction()
    {
        // This line will throw a NullReferenceException
        var customerId = (int)ScenarioContext.Current["wrongKey"];

    }
}

The two problems that I mention are highlighted above. Firstly you have to remember the magic string that you used in the ScenarioContext. You also have to know the type to cast it. The above code will compile but will throw an error at runtime. Runtime errors are much harder to catch than compile time errors. Where possible it’s best to make the compiler work for you.

To aid with that I have written a nuget package called StronglyTypedContext. This nuget package provides you with the ability to have strongly typed scenario contexts alleviating both of the problems above.

Example using StronglyTypedContext nuget package:


public interface IContext
{
    public int CustomerId { get; set; }
}

[Binding]
public class Steps : BindingBase
{

    [ScenarioContext]
    public virtual IContext Context { get; set; ]

    [Given("I have done something")]
    public GivenIHaveDoneSomething()
    {
        Context.CustomerId = 1234;
    }

    // then further down
    [When("I do this action")]
    public WhenIDoThisAction()
    {
        var customerId = Context.CustomerId;

    }
}

Look how much cleaner the code above is. No casting is necessary and we still have access to our ScenarioContext variables throughout the test. The heavy lifting for this is done using a constructor the base class (BindingBase) which uses Castle Dynamic Proxy at runtime. Note how in the code above nowhere do we actually implement the IContext interface?

So how does this work?

1) The abstract class BindingBase has a constructor that looks for public virtual properties marked with ScenarioContextAttribute

2) Castle Dynamic Proxy is then used to generate a proxy implementation for each property and sets each property to the created proxy. The proxy is created with a custom interceptor set.

3) The custom interceptor is invoked when you call get or set on the public virtual property. For example in the code snippet above when you call Context.CustomerId you are actually invoking CustomerId on the proxy which gives the interceptor a chance to get involved.

4)  The interceptor then checks to see if the property is being get or set. It then either retrieves or stores the value in the SpecFlow Scenario context based on a key that is generated from a combination of the type and property name.

I wrote the whole library using a TDD approach.  You can see the full source code on the github page. If you are interested in digging in more detail I would suggest you start by looking at the ProxyInterceptor class. This is where the magic happens.

Advertisements