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(); } } }
One thought on “ScenarioContext in SpecFlow”