ScenarioContext in SpecFlow

I’ve been using SpecFlow pretty regularly for a few weeks now, and I must say, I’m a fan. I find that it’s a lot easier to do test-first development because I’m writing the test in human-readable, business language.

One of the ideas that I didn’t understand right away was how to re-use generated step definitions across features and scenarios. ScenarioContext and FeatureContext give you great options to handle this, though. Let’s check out an example using a modified version of the default scenario SpecFlow generates with a new feature file:

Scenario: Add two numbers
	Given I enter 50 into the calculator
	And I press plus
	And I enter 70 into the calculator
	When I press enter
	Then the result should be 120 be displayed

When I generate step definitions, I might end up with a class that looks like this:

namespace adamprescott.net.Calculator.SpecFlow
{
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using TechTalk.SpecFlow;

    [Binding]
    public class Calculator_AddSteps
    {
        Calculator calculator = new Calculator();

        [Given(@"I enter (.*) into the calculator")]
        public void GivenIEnterIntoTheCalculator(int p0)
        {
            calculator.Number(p0);
        }

        [Given(@"I press plus")]
        public void GivenIPressPlus()
        {
            calculator.Plus();
        }

        [When(@"I press enter")]
        public void WhenIPressEnter()
        {
            calculator.Enter();
        }

        [Then(@"the result should be (.*) be displayed")]
        public void ThenTheResultShouldBeBeDisplayed(int p0)
        {
            Assert.AreEqual(Convert.ToString(p0), calculator.Display);
        }
    }
}

Okay, not bad. A logical next feature might be subtraction. Some of the steps, like entering numbers and pressing enter, are shared. It would be nice if we could re-use those, but they’re configured to manipulate private variables in the Calculator_AddSteps class. So let’s do some refactoring! Instead of using a member-level variable, I can store my Calculator object in the ScenarioContext, making it accessible to other steps being executed in the same scenario.

// store to ScenarioContext like this:
ScenarioContext.Current.Set<Calculator>(new Calculator());
ScenarioContext.Current.Set<Calculator>(new Calculator(), "Calc");
ScenarioContext.Current["Calc"] = new Calculator();

// retrieve from ScenarioContext like this:
var c = ScenarioContext.Current.Get<Calculator>();
var c = ScenarioContext.Current.Get<Calculator>("Calc");
var c = ScenarioContext.Current["Calc"] as Calculator;

This is overkill for such a simple example, but I separated my shared steps into a new step definitions file. The final solution has three classes for the step definitions: Calculator_AddSteps, Calculator_SubtractSteps, and Calculator_SharedSteps.

Here’s the final solution, broken up by file:

Calculator_Add.feature

Feature: Calculator_Add
	In order to avoid silly mistakes
	As a math idiot
	I want to be told the sum of two numbers

Background:
	Given I have a calculator

Scenario: Add two numbers
	Given I enter 50 into the calculator
	And I press plus
	And I enter 70 into the calculator
	When I press enter
	Then the result should be 120 be displayed

Calculator_AddSteps.cs

namespace adamprescott.net.Calculator.SpecFlow
{
    using TechTalk.SpecFlow;

    [Binding]
    public class Calculator_AddSteps
    {
        [Given(@"I press plus")]
        public void GivenIPressPlus()
        {
            ScenarioContext.Current.Get<Calculator>().Plus();
        }
    }
}

Calculator_SharedSteps.cs

namespace adamprescott.net.Calculator.SpecFlow
{
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using System;
    using TechTalk.SpecFlow;

    [Binding]
    public class Calculator_SharedSteps
    {
        [Given(@"I have a calculator")]
        public void GivenIHaveACalculator()
        {
            ScenarioContext.Current.Set<Calculator>(new Calculator());
        }

        [Given(@"I enter (.*) into the calculator")]
        public void GivenIEnterIntoTheCalculator(int p0)
        {
            ScenarioContext.Current.Get<Calculator>().Number(p0);
        }

        [When(@"I press enter")]
        public void WhenIPressEnter()
        {
            ScenarioContext.Current.Get<Calculator>().Enter();
        }

        [Then(@"the result should be (.*) be displayed")]
        public void ThenTheResultShouldBeBeDisplayed(int p0)
        {
            Assert.AreEqual(Convert.ToString(p0), ScenarioContext.Current.Get<Calculator>().Display);
        }
    }
}

Calculator_Subtract.feature

Feature: Calculator_Subtract
	In order to avoid silly mistakes
	As a math idiot
	I want to be told the difference between two numbers

Background:
	Given I have a calculator

Scenario: Subtract two numbers
	Given I enter 70 into the calculator
	And I press minus
	And I enter 50 into the calculator
	When I press enter
	Then the result should be 20 be displayed

Calculator_SubtractSteps.cs

namespace adamprescott.net.Calculator.SpecFlow
{
    using TechTalk.SpecFlow;

    [Binding]
    public class Calculator_SubtractSteps
    {
        [Given(@"I press minus")]
        public void GivenIPressMinus()
        {
            ScenarioContext.Current.Get<Calculator>().Minus();
        }
    }
}
Advertisements

One thought on “ScenarioContext in SpecFlow”

Leave a comment

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s