12 November 2013
2 min readSouth Colorado .NET User Group - SpecFlow

Ehren Dames

For the South Colorado .NET User Group meeting, we had Rudy Lacovara speak on SpecFlow. SpecFlow follows much of the Behavior Driven Development model. Other examples of this in the wild are Cucumber/Gerkin in the Ruby community.
SpecFlow allows developers to write automated tests in a "natural language that can be understood and written by anyone".
To install SpecFlow, you need the NuGet package and the extension manager. (See the installation documentation)
SpecFlow creates integration tests. Basically, you are executing parts of the system that run in some part of the environment. Hitting the database and saving real data. Getting and cleaning up real data.
Below is an example Feature File (Gerkin)
@CompanyFeature: CompanySaveScenario: Anonymous user submits a valid companyGiven user is not logged inAnd they create a company with name, address and descriptionWhen they save the companyThen the company is saved to the databaseAnd the saved company has a status of PendingReviewScenario: Authenticated jobseeker submits a valid companyGiven user is authenticated jobseekerAnd they create a company with name, address and descriptionWhen they save the companyThen the company is saved to the databaseAnd the saved company has a status of PendingReviewScenario: Authenticated employer submits a valid companyGiven user is authenticated employerAnd they create a company with name, address and descriptionWhen they save the companyThen the company is saved to the databaseAnd the saved company has a status of PendingReviewScenario: Authenticated employer requires Address1Given user is Authenticated EmployerWhen they create a company with blank dataThen they run validation for the companyAnd a validation error is thrown for field "CompanyAddress1"Scenario: Authenticated employer requires CityGiven user is Authenticated EmployerWhen they create a company with blank dataThen they run validation for the companyAnd a validation error is thrown for field "CompanyCity"Scenario: Authenticated employer requires StateGiven user is Authenticated EmployerWhen they create a company with blank dataThen they run validation for the companyAnd a validation error is thrown for field "CompanyState"
You can also use a table to reduce the number of scenarios written for validation. Above three scenarios were written for Company Address, City and State.
Scenario Outline: SubmitCompany validation requires field - OutlineGiven user is Authenticated EmployerAnd then create a company with blank dataWhen they run validation for the companyThen a valadation error is thrown for Field "<fieldName>"Example:|fieldName ||CompanyAddress1 ||CompanyAddress2 ||CompanyCity ||CompanyState ||CompanyName |
Next, we generate a Definition File which will contain our C# code for our steps in our scenarios.
To do this, you can use "Generate Step Definitions" to stub out the steps in the feature. Part of this process to generate the required methods uses regular expressions to wire up these definitions from the feature file.
Rudy uses a TestHelper class to better manage setting up a company for the tests. He also recommends using an AuthTokenClass for credential management.
public class CompanySaveSteps {private dynamic context = new ExpandoObject();private CompanyService companyService = new CompanyService();[Given(@"user is not logged in")]public void GivenUserIsNotLoggedIn() {context.AuthToken = TestHelper.GetAnonymousJobseekerAuthToken();ScenarioContext.Current.Pending();}[Given(@"they create a company with name, address and description")]public void GivenTheyCreatedCompanyWithNameAddressAndDescription(){var company = TestHelper.GetTestCompany();company.CompanyAddress1 = "1600 Pennsylvania Ave";company.Description = "My description";context.Company = company;}[Given(@"user is authenticated jobseeker")]public void GivenUserIsAuthenticatedJobseeker(){context.AuthToken = TestHelper.GetAuthenticatedJobseekerAuthToken();}[Given(@"user is authenticated employer")]public void GivenUserIsAuthenticatedEmployer(){context.AuthToken = TestHelper.GetAuthenticatedEmployerAuthToken();}[When(@"they save the company")]public void WhenTheySaveTheCompany{companyService.Save(context.Company, context.AuthToken);}[When(@"they create a company with blank data")]public void WhenTheyCreateACompanyWithBlankData(){}[BeforeScenario]public void BeforeScenario(){companyService.Delete(TestHelper.GetTestCompany(), TestHelper.GetAdminAuthToken());}[Then(@"the company is saved to the database")]public void ThenCompanyIsSavedToTheDatabase(){context.CheckComany = companyService.Getcompany(context.Compnay.CompanyGuid);Assert.IsNotNull(context.CheckCompany, "There's no company man");}[Then(@"The saved company has a status of pending review)]public void ThenTheSavedCompanyHasAStatusOfPendingReview() {Assert.IsTrue(context.CheckCompany.Status == CompanyStatus.PendingReview, "wrong status man!");}[When(@"They run validation for the compnay]public void WhenTheyRunValidationForTheCompany(){var validator = new CompnayValidator();try {validator.Validate(context.Company);}catch(RuleException ex){context.RuleException = ex;}}[Then(@"a validation error is thrown for field ""(.*)"""]public void ThenValidationErrorIsThrownForField(string fieldName){var ex = context.RuleException as RuleExceptionAssert.IsTrue(ex.ErrorList.FirstOrDefault(m => m.FieldName == fieldName)!=null, "no rule exception found");}
If you notice in the code sample above each method relates to a step in the scenarios. You will also we have simple tear down and setup per run. BeforeScenario method allows us to clean out the company.