Skip to main content

Coming from SpecFlow

SpecFlow (and Reqnroll) bind Gherkin text to C# methods with regular expressions and generate test classes from .feature files. BDTest skips the text layer: the C# chain is the scenario, and the report text comes from attributes or method names, read at compile time by the source generator bundled in BDTest.

Concept map​

SpecFlow / GherkinBDTest
.feature fileA test class
Feature: with "As a / I want / So that"[Story(AsA = ..., IWant = ..., SoThat = ...)] on the class
Scenario:A test method with [ScenarioText("...")]
Given / When / Then / And / But linesGiven(...), When(...), Then(...), .And(...), .But(...)
[Binding] step definitions with regexOrdinary methods, optionally with [StepText("... {parameterName} ...")]
Step argument transformationsTyped arguments, plus IStepTextStringConverter<T> for display
ScenarioContext / context injectionThe per-test Context object
Scenario Outline: + Examples:ScenarioOutline(...) with typed rows
@tags[BDTestTag("...")]
[BeforeStep] / [AfterStep] hooksOnBeforeStep / OnAfterStep or IStepHook registrations on BDTestOptions.StepHooks
[BeforeScenario] / [AfterScenario]Your framework's per-test hooks, such as TUnit's [Before(HookType.Test)] / [After(HookType.Test)]
@ignoreYour framework's ignore attribute, or a skip rule
ScenarioContext.Pending()Throw NotImplementedException: the step becomes NotImplemented
LivingDoc / reportsThe HTML report or report server

Example​

Feature: Withdrawals
As an account holder
I want to withdraw money
So that I can use my savings

Scenario: Withdrawing money reduces the balance
Given I have an account with a balance of 100
When I withdraw 25
Then the account balance is 75

becomes:

Illustrative fragment
[Story(AsAn = "account holder", IWant = "to withdraw money", SoThat = "I can use my savings")]
public class WithdrawalTests : TUnitBDTestBase<AccountContext>
{
private AccountSteps Account => new(Context);

[Test]
[ScenarioText("Withdrawing money reduces the balance")]
public async Task WithdrawingMoneyReducesTheBalance()
{
await Given(() => Account.CreateAnAccount(100m))
.When(() => Account.Withdraw(25m))
.Then(() => Account.TheBalanceIs(75m));
}
}

The step definitions become methods with [StepText]:

Illustrative fragment
public class AccountSteps(AccountContext context)
{
[StepText("I have an account with a balance of {balance}")]
public void CreateAnAccount(decimal balance) => context.Balance = balance;

[StepText("I withdraw {amount}")]
public void Withdraw(decimal amount) => context.Balance -= amount;

[StepText("the account balance is {expected}")]
public async Task TheBalanceIs(decimal expected) => await Assert.That(context.Balance).IsEqualTo(expected);
}

Migration tips​

  • Migrate one feature at a time. BDTest and SpecFlow tests can live in the same solution, in separate projects.
  • Turn each regex step definition into a method with a readable name. Often the method name alone produces good step text, so [StepText] is optional.
  • Move ScenarioContext keys into properties on a typed context class.
  • Replace table arguments with typed objects or collections.
  • Use .When(...).And(...).Then(...) for scenarios with multiple actions. And and But keep the current phase.