| name | specflow-reqnroll |
| description | When the user wants to design, implement, debug, or migrate BDD tests on .NET using SpecFlow or its open-source successor Reqnroll. Use when the user mentions "SpecFlow," "Reqnroll," "[Binding]," "[Given]," "[When]," "[Then]," ".feature file in .NET," "SpecFlow+ LivingDoc," "SpecFlow.Tools.MsBuild.Generation," "Reqnroll migration," or ".NET BDD." For language-agnostic Gherkin patterns see cucumber-gherkin. For Java BDD see cucumber-gherkin (Cucumber-JVM). For Python BDD see behave. For BDD failure modes see bdd-anti-patterns. |
| metadata | {"version":"1.0.0"} |
SpecFlow & Reqnroll (.NET BDD)
You are an expert in BDD on .NET — historically via SpecFlow, and going forward via Reqnroll (the open-source successor maintained by the community after SpecFlow's commercial wind-down). Your goal is to help engineers run, maintain, and migrate .NET BDD suites without fabricating attribute names, NuGet package IDs, or migration tooling. When uncertain, point the reader to reqnroll.net or the archived SpecFlow docs.
Initial Assessment
Check .agents/qa-context.md (fallback: .claude/qa-context.md) before answering. Pay attention to:
- SpecFlow status — Tricentis announced SpecFlow's end-of-life. Reqnroll is the actively-maintained, open-source, near-drop-in fork. New projects should use Reqnroll; existing SpecFlow projects should plan migration.
- .NET version — Reqnroll targets modern .NET (6/7/8/9). Older SpecFlow installations may be pinned to .NET Framework.
- Test runner — xUnit, NUnit, or MSTest. Both SpecFlow and Reqnroll integrate with all three.
- Migration state — is the team on SpecFlow today, planning Reqnroll migration, or starting fresh?
- Reporting — SpecFlow+ LivingDoc was a paid feature; Reqnroll has community-equivalent open-source options.
If the file does not exist, ask: SpecFlow or Reqnroll, .NET version, test runner, current/planned reporting tool.
The SpecFlow → Reqnroll situation
In 2024, Tricentis announced SpecFlow would be deprecated. Reqnroll forked SpecFlow's codebase and continues active development as an open-source project. For most teams:
- New project: use Reqnroll.
- Existing SpecFlow project: plan a Reqnroll migration. The migration is typically straightforward (rename NuGet packages, rename namespaces — the surface API is highly compatible).
- Don't pretend SpecFlow has a long future: even if your current build still works, the package will not get fixes for new .NET versions indefinitely.
Reqnroll keeps the same Gherkin features and the same [Binding] / [Given] / [When] / [Then] attribute model as SpecFlow — most existing code compiles after a namespace swap.
Project setup (Reqnroll)
NuGet packages (verify current versions):
<PackageReference Include="Reqnroll" Version="<pinned-version>" />
<PackageReference Include="Reqnroll.xUnit" Version="<pinned-version>" />
<PackageReference Include="Reqnroll.Tools.MsBuild.Generation" Version="<pinned-version>" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="<pinned-version>" />
<PackageReference Include="xunit" Version="<pinned-version>" />
<PackageReference Include="xunit.runner.visualstudio" Version="<pinned-version>" />
.feature files live in the test project. Reqnroll's MSBuild generator produces code-behind files at build time.
For SpecFlow, replace Reqnroll → SpecFlow in the package names; the rest is structurally identical.
A feature
# Checkout.feature
Feature: Checkout
As a registered customer
I want to complete a purchase
So that I can receive my order
Background:
Given I am signed in as a regular customer
@smoke
Scenario: Successful card purchase
Given my cart has one Widget priced at 19.99 USD
When I submit checkout with a valid card
Then the order is placed
Scenario Outline: Tax by region
Given my cart total is 100.00 USD
When I check out from <region>
Then the displayed tax is <tax>
Examples:
| region | tax |
| Oregon | 0.00 USD |
| New York | 8.875 USD |
Step definitions
using Reqnroll;
using FluentAssertions;
[Binding]
public class CheckoutSteps
{
private readonly ScenarioContext _scenarioContext;
private readonly Cart _cart;
private readonly Checkout _checkout;
public CheckoutSteps(ScenarioContext scenarioContext, Cart cart, Checkout checkout)
{
_scenarioContext = scenarioContext;
_cart = cart;
_checkout = checkout;
}
[Given(@"I am signed in as a regular customer")]
public void SignedInAsCustomer() => _checkout.SignIn("qa.user@example.com");
[Given(@"my cart has one Widget priced at ([\d.]+) USD")]
public void CartHasWidgetAt(decimal price) => _cart.Add(new Widget(), price);
[When(@"I submit checkout with a valid card")]
public void SubmitWithValidCard() => _checkout.Submit(TestCards.Valid);
[Then(@"the order is placed")]
public void OrderIsPlaced() => _checkout.LatestOrder.Should().NotBeNull();
}
Step definitions are decorated [Binding] classes. Reqnroll injects constructor dependencies — use them for shared state across step classes within a scenario.
Scenario context and dependency injection
| Mechanism | Use |
|---|
ScenarioContext | Per-scenario dictionary-like storage. |
FeatureContext | Per-feature storage. Avoid mutating from steps. |
| Constructor DI | Recommended pattern — Reqnroll/SpecFlow inject types you register. |
[BeforeScenario] / [AfterScenario] | Hooks. |
[BeforeTestRun] / [AfterTestRun] | Once-per-run hooks (static). |
Use constructor DI for typed state — Cart, Checkout, etc. — and ScenarioContext for ad-hoc key/value carrying within a scenario.
[Binding]
public class Hooks
{
[BeforeScenario("@auth")]
public void EnsureSignedIn(ScenarioContext context) { }
[AfterScenario]
public void CaptureFailure(ScenarioContext context)
{
if (context.TestError != null) { }
}
}
Parameter binding
[Given(@"my cart total is (\d+\.\d+) (USD|EUR|CAD)")]
public void CartTotalIs(decimal total, string currency) { }
For typed enums / domain objects, register a [StepArgumentTransformation]:
[StepArgumentTransformation]
public Money ConvertMoney(string raw) => Money.Parse(raw);
[Given(@"my cart total is (.+)")]
public void CartTotalIs(Money total) { }
Cleaner step text and reusable conversion.
Tag-based selection
dotnet test --filter "TestCategory=smoke"
dotnet test --filter "Category=smoke"
Tags from @smoke become test categories in the generated test methods. The exact filter syntax varies by runner — verify against your runner's docs.
Reports
- SpecFlow+ LivingDoc (commercial, archived) — was the gold standard. New projects should use community alternatives.
- Reqnroll LivingDoc / community equivalents — actively evolving open-source options.
- Allure integrates with all three runners.
- JUnit XML via
xunit.runner.visualstudio / nunit --logger junit;....
For CI dashboards, JUnit XML is the lowest common denominator.
Running
dotnet test
dotnet test --filter "Category=smoke"
dotnet test --filter "FullyQualifiedName~Checkout"
dotnet test --logger "junit;LogFilePath=results.xml"
Verify with dotnet test --help against your installed SDK.
Migrating SpecFlow → Reqnroll
Typical steps (verify against current Reqnroll migration docs):
- Replace NuGet packages:
SpecFlow.* → Reqnroll.*.
- Update namespaces in step definition files:
TechTalk.SpecFlow → Reqnroll.
- Update
specflow.json → reqnroll.json (settings file rename).
- Rebuild — the code-behind generator runs and produces Reqnroll-compatible output.
- Run the suite; investigate any test changes (most pass through unmodified).
For mixed migration, both packages can't coexist in the same project — migrate one project at a time.
Common Pitfalls
- Staying on SpecFlow indefinitely — future .NET versions won't be supported. Plan migration.
- Static state in step classes — leaks across scenarios in parallel runs. Use DI.
FeatureContext mutation from steps — meant for feature-level read-only data, not scenario state.
- One mega step class with 50 bindings — split by domain.
- Regex bindings without parameter types — error-prone. Use parameter types when possible.
[BeforeTestRun] doing expensive setup that should be per-scenario — slow startup, leaky state.
- Same step text bound in multiple classes — runtime ambiguity error. Cucumber/SpecFlow/Reqnroll all fail when bindings collide.
- Asserting on UI text in
Then — localization break.
- Migrating SpecFlow→Reqnroll without pinning Reqnroll version — pin, test, then unpin only when comfortable.
Task-Specific Questions
When helping with SpecFlow / Reqnroll, ask:
- SpecFlow (current version) or Reqnroll? Migration planned?
- .NET version?
- Test runner — xUnit, NUnit, MSTest?
- DI framework — Reqnroll's built-in, custom (Autofac, Microsoft.Extensions.DependencyInjection)?
- Reporting target — JUnit XML for CI, LivingDoc, Allure?
- Parallelism strategy — assembly / class / collection level?
- UI / API driver under steps?
Related Skills
- cucumber-gherkin — the cross-language Gherkin canon; principles transfer.
- bdd-anti-patterns — read alongside; same failures recur in .NET BDD.
- xunit-nunit — for the underlying test-runner specifics.
- behave — Python BDD equivalent.
- selenium / playwright — common UI drivers under steps.
- rest-assured equivalent: REST testing in .NET typically uses
RestSharp or HttpClient directly inside step defs.
- ci-test-orchestration — for parallel runs and matrix sharding.
- test-strategy — for placing BDD in the pyramid.