Steps are the building blocks of SpecFlow. Each Given/When/Then line in a SpecFlow scenario represents a step, and steps should be reused across features and scenarios to test your application from different angles. When you’re building a low-level scenario, you may want to use very specific steps. In a higher-level feature, you may want to perform the same tasks but in a less granular fashion. Wouldn’t it be nice if you could create a “super-step” that just calls the necessary sub-steps?
Well, guess what? You can, and it’s really easy to do. First, let’s build some fake code to work with. I created a simple PersonRepository that lets me add and save Person objects. Here are the classes and my initial SpecFlow test.
Person.cs
using System;
namespace samples.SpecFlowDemo
{
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime? DateOfBirth { get; set; }
}
}
PersonRepository.cs
using System;
using System.Collections.Generic;
namespace samples.SpecFlowDemo
{
public static class PersonRepository
{
private static readonly Dictionary<int, Person> Persons;
static PersonRepository()
{
Persons = new Dictionary<int, Person>();
}
public static Person Get(int id)
{
if (!Persons.ContainsKey(id))
{
return null;
}
return Persons[id];
}
public static bool Save(Person person)
{
if (person == null)
{
return false;
}
if (!Persons.ContainsKey(person.Id))
{
Persons.Add(person.Id, person);
}
Persons[person.Id] = person;
return true;
}
}
}
Person_Add.feature
Feature: Person_Add
In order track person records
As a records manager
I need to add new persons
Scenario: Add a person
Given I have a new person record with the following properties
| id | name | date of birth |
| 100 | Rodney | 2/20/1950 |
When I save the person
Then the person is saved successfully
And I can retrieve the person by ID
Person_AddSteps.cs
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TechTalk.SpecFlow;
using TechTalk.SpecFlow.Assist;
namespace samples.SpecFlowDemo.SpecFlow
{
[Binding]
public class Person_AddSteps
{
[Given(@"I have a new person record with the following properties")]
public void GivenIHaveANewPersonRecordWithTheFollowingProperties(Table table)
{
ScenarioContext.Current.Set<Person>(
table.CreateInstance<Person>());
}
[When(@"I save the person")]
public void WhenISaveThePerson()
{
var r = PersonRepository.Save(
ScenarioContext.Current.Get<Person>());
ScenarioContext.Current.Set<bool>(r, "SaveResult");
}
[Then(@"the person is saved successfully")]
public void ThenThePersonIsSavedSuccessfully()
{
Assert.IsTrue(
ScenarioContext.Current.Get<bool>("SaveResult"),
"SaveResult");
}
[Then(@"I can retrieve the person by ID")]
public void ThenICanRetrieveThePersonByID()
{
var expected = ScenarioContext.Current.Get<Person>();
var actual = PersonRepository.Get(expected.Id);
Assert.AreSame(expected, actual);
}
}
}
Now let’s say I want to test the PersonRepository’s ability to update records. In order to update a record, the record needs to exist. I could reuse the add feature’s “I have a person” step with a provided table of properties and its “I save the person” step, but it would be nice if I didn’t have to call both of those steps and provide data each time I needed to do something with an existing record.
I can avoid the repetition by calling those steps from within a new step that I’ll create called “I have an existing person record.” Here’s the code for my update feature.
Person_Update.feature
Feature: Person_Update
In order track person records
As a records manager
I need to update existing persons
Scenario: Update a person
Given I have an existing person record
When I change the person name to "Rocko"
And I save the person
Then the person is saved successfully
And I can retrieve the person by ID
And the person name was saved as "Rocko"
Person_UpdateSteps.cs (Note the highlighted lines. The only “gotcha” that I ran into is that the steps class must inherit from SpecFlow’s Steps class in order to access the Given/When/Then functions.)
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TechTalk.SpecFlow;
namespace samples.SpecFlowDemo.SpecFlow
{
[Binding]
public class Person_UpdateSteps : Steps
{
[Given(@"I have an existing person record")]
public void GivenIHaveAnExistingPersonRecord()
{
var header = new[] { "Field", "Value" };
var t = new Table(header);
t.AddRow("id", "100");
t.AddRow("name", "Fred");
t.AddRow("date of birth", "12/15/1990");
Given("I have a new person record with the following properties", t);
When("I save the person");
}
[When(@"I change the person name to ""(.*)""")]
public void WhenIChangeThePersonNameTo(string p0)
{
var p = ScenarioContext.Current.Get<Person>();
p.Name = p0;
}
[Then(@"the person name was saved as ""(.*)""")]
public void ThenThePersonNameWasSavedAs(string p0)
{
var p = ScenarioContext.Current.Get<Person>();
Assert.AreEqual(p0, p.Name);
}
}
}
You can read more about calling steps from step definitions in the SpecFlow documentation.
You can also download the code for this example from GitHub.
Like this:
Like Loading...