Browse the Knowledge Hub211 resources

SpecFlow / Reqnroll review: what it is and how to move to Reqnroll
SpecFlow was the standard BDD framework for .NET, binding Gherkin scenarios to C# step definitions. Tricentis ended it on 31 December 2024. Reqnroll, started from the SpecFlow codebase by SpecFlow’s creator, is the maintained continuation: open source under BSD 3-Clause, largely compatible, and running on NUnit, MSTest, xUnit or TUnit.
QAble has evaluated this tool and works alongside it. The factual sections are complete; our view is bounded to what we have actually seen rather than a long-term verdict.
- last reviewed
- 13 October 2026
- version covered
- Reqnroll 3 (SpecFlow is end of life)
- licence
- Open source
At a glance
SpecFlow / Reqnroll at a glance
- Vendor
- Reqnroll community
- Category
- BDD frameworks
- Licence
- Open source (BSD 3-Clause (Reqnroll))
- Platforms
- .NET, .NET Framework 4.6.2 or later, Windows, Linux, macOS
- Languages
- Gherkin, C#
- Integrates with
- NUnit, MSTest, xUnit, TUnit, Visual Studio, Rider, Selenium, Playwright
- First released
- 2009
- Version reviewed
- Reqnroll 3 (SpecFlow is end of life)
Verdict
Our verdict on SpecFlow / Reqnroll
Strengths
- Migration from SpecFlow is mostly renamed packages and namespaces, and a compatibility package keeps TechTalk.SpecFlow working meanwhile
- Runs on NUnit, MSTest, xUnit (v2 and v3) or TUnit, so it fits the test runner a team already uses
- Cucumber Expressions are built in, giving typed step parameters such as {int} and {string} without regular expressions
- Built-in HTML and Cucumber Messages formatters since v3 replace the closed-source SpecFlow+ LivingDoc
- Context injection shares state between binding classes through constructors, with one instance per scenario
- Free under BSD 3-Clause, with Visual Studio, Rider and VS Code support
Weaknesses
- SpecFlow itself is end of life: no fixes, no support beyond .NET 7, and no official source repository
- Maintained by a small sponsor-funded team, so release cadence depends on community funding
- Code-behind generation at build time adds a step between editing a feature and running it
- Most tutorials online still show SpecFlow namespaces and LivingDoc, which no longer apply
Our take
For a .NET team today the decision is not whether to use SpecFlow but when to move to Reqnroll. SpecFlow received its last updates before Tricentis ended it on 31 December 2024, its GitHub repositories were deleted, and it is not updated for .NET versions after 7 or for current IDEs. Reqnroll is a low-risk move: the migration is mostly package and namespace changes, and a compatibility package removes even those. QAble has evaluated Reqnroll rather than maintained a long-running suite on it, so treat this as a bounded take. Two cautions. Reqnroll is maintained by a small team funded by sponsors, so check release activity before committing a large estate. And everything true of Cucumber applies: the tool does not create collaboration, and Gherkin written after the code is automation with extra ceremony.
We have evaluated this tool rather than maintained a long-running suite on it, so this is a bounded view rather than a long-term verdict. The rest of this page is factual.
How it works
What is SpecFlow / Reqnroll and how does it work?
Feature files become generated test classes
Reqnroll does not run feature files directly. An MSBuild task runs at build time and generates a code-behind file for each feature, a C# class with one test method per scenario and one per Scenario Outline example, decorated with the attributes of the chosen test framework. NUnit, MSTest, xUnit or TUnit then discovers and runs those methods like any other test. That is why scenarios appear in Test Explorer and dotnet test without a special runner, and why a feature edit needs a build before it runs. From v3.3 the generated files can live in the obj folder, which keeps them out of source control and is planned as the default for v4.
Bindings matched by attribute
Step definitions are public methods on classes marked [Binding], tagged with [Given], [When] or [Then] and an expression. The expression is either a Cucumber Expression such as the client added {int} pcs of {string}, or a regular expression; Reqnroll decides which by its shape, and anchoring with ^ and $ forces a regex. At run time each scenario step is matched against all bindings in the configured assemblies, so step text is a shared vocabulary across the project, as in Cucumber. Hooks such as [BeforeScenario] and [AfterTestRun] are bindings too, and can be scoped to tags.
A container per scenario
Reqnroll includes a small dependency injection container, Reqnroll.BoDi, formerly BoDi in SpecFlow. For each scenario it creates binding class instances and any context classes they ask for in their constructors, and disposes them when the scenario ends. ScenarioContext and FeatureContext are injectable the same way, through their interfaces from v3. Plugins exist for Autofac and Microsoft.Extensions.DependencyInjection when the application’s own container should supply services. Because state lives in per-scenario objects rather than statics, scenarios stay isolated, which is also what makes parallel execution safe.
Parallelism belongs to the test runner
Reqnroll has no runner of its own: scenarios are ordinary tests in the chosen framework, so parallel execution is therefore whatever the chosen framework provides. NUnit, MSTest and TUnit can run individual scenarios in parallel, while xUnit runs features in parallel with scenarios of one feature on the same thread. Reqnroll isolates each thread’s ScenarioContext, but the binding registry and BeforeTestRun hooks are shared, and the documentation advises against relying on FeatureContext and feature hooks when scenarios of one feature can be spread across threads.
Strengths
What it does well
A drop-in path off SpecFlow
Reqnroll was created from the SpecFlow v4 codebase, so feature files, bindings, hooks, tables and configuration carry over. reqnroll.json accepts the specflow.json format, and Reqnroll.SpecFlowCompatibility maps the old TechTalk.SpecFlow namespace, so many projects run after changing package references alone. Ported plugins, including the SpecFlow.Actions packages, cover common integrations.
Choice of test framework
Reqnroll.NUnit, Reqnroll.MsTest, Reqnroll.xUnit, Reqnroll.xunit.v3 and Reqnroll.TUnit generate tests for each framework. Teams keep their existing runner, CI reporting and assertion library, and the documentation’s guidance is simple: pick the framework you know, and NUnit if you have no preference.
Context injection for shared state
Any plain class declared as a constructor parameter on a binding class is created once per scenario and shared by every binding that asks for it, then disposed. This is the clean way to pass a customer, an API response or a page object between step classes without static fields, and it keeps parallel runs isolated.
Table helpers for structured input
Data tables arrive as a DataTable, an alias for the SpecFlow Table class, and the Assist helpers such as CreateInstance, CreateSet and CompareToSet convert rows to objects or compare results against expected rows. That keeps examples readable in the feature file without parsing code in every step.
HTML and Cucumber Messages reports
Reqnroll 3 added formatters. The HTML formatter writes a single-page report of features and results using Cucumber’s report components, the documented replacement for SpecFlow+ LivingDoc. The Message formatter writes Cucumber Messages as newline-delimited JSON, which other tools can consume. Both are enabled in reqnroll.json or by environment variables.
IDE support across platforms
The Reqnroll extension for Visual Studio 2022 and 2026 adds syntax colouring, navigation from steps to bindings and a Define Steps command that generates snippets, and it handles SpecFlow projects too. A Reqnroll plugin exists for Rider, and Visual Studio Code works with the official Cucumber extension.
Pricing
SpecFlow / Reqnroll pricing and licensing
Reqnroll is free and open source under the BSD 3-Clause licence, with no paid edition. The project is funded by sponsors, listed on its website and repository. SpecFlow’s open source core was also free; its closed-source SpecFlow+ LivingDoc report did not carry over, and Reqnroll’s built-in HTML formatter takes its place. Costs are engineering time: writing and maintaining scenarios and bindings, and the migration from SpecFlow.
- Licence: BSD 3-Clause for Reqnroll, stated in the repository.
- No tiers: every test framework integration, the formatters and the IDE extensions are free.
- SpecFlow: end of life since 31 December 2024. Existing packages remain on NuGet but receive no updates.
- Migration cost: usually small. Package references and namespaces change, or the compatibility package avoids the namespace change; projects coming from SpecFlow v3 also meet the v3 to v4 breaking changes.
- Hidden cost: the glue layer. Every automated check needs a feature file and bindings as well as test logic, which only pays off when non-developers read the scenarios.
Checked against the vendor’s own pages on 13 October 2026. Pricing changes; confirm on the official site before buying.
Getting started
Getting started with SpecFlow / Reqnroll
Setup, step by step
- Install the .NET SDK, and the Reqnroll extension for Visual Studio or the plugin for Rider.
- Install the Reqnroll project templates with dotnet new install.
- Create a project with dotnet new reqnroll-project, choosing the test framework with -t.
- Write a feature with one rule and one concrete example under the Features folder.
- Build and run dotnet test; copy the suggested snippets for undefined steps into a binding class.
- Implement the bindings and run dotnet test again until the scenario passes.
Install
dotnet new install Reqnroll.Templates.DotNet
mkdir ShopSpecs && cd ShopSpecs
# NUnit is the template default; -t mstest, xunit or tunit also work
dotnet new reqnroll-project -t nunit
dotnet testA first example
// Features/Discount.feature
// Feature: Loyalty discount
// Rule: Gold members get 10% off orders over 100
// Scenario: Gold member over the threshold
// Given a gold member
// When they place an order worth 120.00
// Then they pay 108.00
// StepDefinitions/DiscountStepDefinitions.cs
using Reqnroll;
using NUnit.Framework;
namespace ShopSpecs.StepDefinitions;
[Binding]
public class DiscountStepDefinitions
{
// A new instance per scenario, so fields never leak between scenarios
private Customer _customer = null!;
private Order _order = null!;
[Given("a gold member")]
public void GivenAGoldMember() => _customer = Customer.Gold();
// {decimal} converts "120.00" to a decimal argument
[When("they place an order worth {decimal}")]
public void WhenTheyPlaceAnOrder(decimal amount) =>
_order = Checkout.Place(_customer, amount);
[Then("they pay {decimal}")]
public void ThenTheyPay(decimal expected) =>
Assert.That(_order.Total, Is.EqualTo(expected));
}What trips people up first
Two extensions fighting over feature files. The Reqnroll extension for Visual Studio and Rider cannot run alongside the SpecFlow one, because both process .feature files; symptoms are missing colouring, broken navigation or code-behind errors. Remove or disable the SpecFlow extension, since the Reqnroll one also handles SpecFlow projects. The other first-hour trap is stale generated code: scenarios are compiled from code-behind files at build time, so a feature edit that has not been built runs the old version, and code-behind files committed from a SpecFlow project can clash with the newly generated ones. Rebuild, and keep generated files out of source control.
In practice
Using SpecFlow / Reqnroll in practice
Migrating a SpecFlow project to Reqnroll
There are two documented paths. The quick one replaces every SpecFlow package with its Reqnroll equivalent and adds Reqnroll.SpecFlowCompatibility, which keeps the TechTalk.SpecFlow namespace and even App.config working; the guide says that for most projects this is the only change. The complete one also replaces TechTalk.SpecFlow with Reqnroll across the code, renames ISpecFlowOutputHelper to IReqnrollOutputHelper, and renames specflow.json to reqnroll.json, where stepAssemblies becomes bindingAssemblies. Remove any CucumberExpressions.SpecFlow packages, since Reqnroll supports Cucumber Expressions natively. Projects still on SpecFlow v3 also inherit the v3 to v4 changes, chiefly that some regex step definitions are now read as Cucumber Expressions. Migrate on a branch, run the suite, and compare scenario counts before and after.
<!-- ShopSpecs.csproj: before, SpecFlow -->
<!-- <PackageReference Include="SpecFlow.NUnit" Version="3.9.74" /> -->
<!-- after: Reqnroll, keeping SpecFlow namespaces for now -->
<ItemGroup>
<!-- Keep your existing NUnit, adapter and Test SDK references -->
<PackageReference Include="Reqnroll.NUnit" Version="3.3.4" />
<!-- Maps TechTalk.SpecFlow to Reqnroll; remove after renaming namespaces -->
<PackageReference Include="Reqnroll.SpecFlowCompatibility" Version="3.3.3" />
</ItemGroup>Sharing state with context injection
Group bindings by domain concept rather than by feature file, because steps are global and a class per feature encourages near-duplicates. Once a scenario uses several binding classes, share state through a context class declared as a constructor parameter: Reqnroll creates one instance per scenario, gives the same instance to every binding class that asks for it, and disposes it if it implements IDisposable. Avoid ScenarioContext.Current and the other static accessors, which do not work with parallel execution, and inject IScenarioContext instead when you need scenario metadata such as tags.
// One instance per scenario, shared by every binding that asks for it
public class ShoppingContext
{
public Customer Customer { get; set; } = null!;
public HttpResponseMessage? LastResponse { get; set; }
}
[Binding]
public class CustomerSteps(ShoppingContext context)
{
[Given("a gold member")]
public void GivenAGoldMember() => context.Customer = TestCustomers.Gold();
}
[Binding]
public class OrderSteps(ShoppingContext context, IScenarioContext scenario)
{
private readonly OrdersClient _orders = new();
[When("they place an order worth {decimal}")]
public async Task WhenTheyPlaceAnOrder(decimal amount)
{
// Async bindings are supported; await rather than .Result
context.LastResponse = await _orders.PlaceAsync(context.Customer.Id, amount);
}
}Running in GitHub Actions with an HTML report
A Reqnroll pipeline is an ordinary dotnet test run. Enable the HTML formatter, either in reqnroll.json or with the REQNROLL_FORMATTERS_HTML environment variable, so every run produces a single-page report next to the test output, and upload it with the test results whether or not the run fails. Filter scenarios by tag through the test framework’s own filter, since Reqnroll turns tags into categories or traits; with NUnit, dotnet test --filter Category=smoke runs the scenarios tagged @smoke. Run a smoke subset on pull requests and the full suite on main or nightly.
# .github/workflows/specs.yml
name: specs
on: [push, pull_request]
jobs:
reqnroll:
runs-on: ubuntu-latest
env:
# Path is relative to the test project's output folder
REQNROLL_FORMATTERS_HTML: outputFilePath=reqnroll_report.html
steps:
- uses: actions/checkout@v7
- uses: actions/setup-dotnet@v6
with:
dotnet-version: '10.0.x'
- run: dotnet restore
- name: Run scenarios
run: dotnet test --no-restore --logger trx --results-directory TestResults
- uses: actions/upload-artifact@v7
if: always()
with:
name: reqnroll-results
path: |
TestResults
**/bin/**/reqnroll_report.htmlParallel execution and isolation
Parallelism is configured in the test framework, not in Reqnroll. With NUnit, an assembly-level Parallelizable attribute with ParallelScope.Fixtures runs features in parallel, and ParallelScope.Children runs individual scenarios. MSTest and TUnit also support scenario-level parallelism; xUnit runs features in parallel. Before enabling it, remove static state, stop using ScenarioContext.Current, and make test data unique per scenario. If some features must not run concurrently, the addNonParallelizableMarkerForTags setting in reqnroll.json marks scenarios with chosen tags as non-parallelisable. Start with feature-level parallelism, which keeps FeatureContext and feature hooks predictable, and move to scenario level only once the suite is clean.
Keeping the suite maintainable
The maintenance problems are the same as Cucumber’s. Write declarative steps about business intent, and push clicks, selectors and HTTP calls into page objects or clients that bindings call. Review new steps against the existing vocabulary so the same idea is not phrased five ways. Use REQNROLL_DRY_RUN=true, added in v3, to check quickly that every step has a binding without executing any code. Keep generated code-behind in the obj folder with ReqnrollUseIntermediateOutputPathForCodeBehind, which v4 is planned to make the default. And watch the deprecations: ScenarioContext.Pending and StepIsPending are deprecated for removal in v4, so throw PendingStepException instead.
Pitfalls
Common pitfalls
Staying on SpecFlow because it still builds
Why it happens: SpecFlow packages remain on NuGet, so existing projects keep compiling. But SpecFlow ended on 31 December 2024, works only up to .NET 7, gets no security fixes and no updates for new Visual Studio or Rider versions.
The fix: Schedule the move to Reqnroll before the next .NET upgrade forces it. Start with the compatibility package path, which usually needs only package changes.
Regex step definitions read as Cucumber Expressions
Why it happens: Reqnroll, like SpecFlow v4, detects the expression type from its shape. A pattern such as I \$ something, written as a regex for SpecFlow v3, can be taken as a Cucumber Expression and fail to match or raise an error.
The fix: Anchor regular expressions with ^ and $ to force regex matching, or set ExpressionType to RegularExpression on the attribute, or rewrite the step as a Cucumber Expression.
Using ScenarioContext.Current with parallel execution
Why it happens: Static accessors such as ScenarioContext.Current and FeatureContext.Current cannot tell which thread’s scenario you mean, so they are unsupported once scenarios run in parallel.
The fix: Inject ScenarioContext or IScenarioContext through the binding constructor, and keep shared data in context classes resolved by context injection.
Code broken by Reqnroll 3 renames
Why it happens: Reqnroll 3 renamed the Regex property and regex parameter on step attributes to Expression, removed the Culture property and the synchronous ISyncTestRunner, and dropped .NET 6 and 7 support.
The fix: Read the v3.0.0 breaking changes in the changelog before upgrading from Reqnroll 2, update custom attributes and plugins, and target a supported .NET version.
Expecting SpecFlow+ LivingDoc after migrating
Why it happens: LivingDoc was part of SpecFlow’s closed-source implementation, so Reqnroll could not take it over. Pipelines that call the LivingDoc generator lose their report after migration.
The fix: Enable the built-in HTML formatter, which the documentation names as the replacement. A documented workaround also lets the SpecFlow LivingDoc CLI read Reqnroll output, but treat it as a stopgap.
FeatureContext surprises under scenario-level parallelism
Why it happens: When a feature’s scenarios are spread across threads, BeforeFeature and AfterFeature run once per thread, and each thread can have its own FeatureContext, so feature-level setup runs more than once.
The fix: Use class-level parallelism if you rely on feature hooks, or move expensive shared setup to BeforeTestRun and keep per-scenario state in injected context classes.
Compared
SpecFlow / Reqnroll alternatives compared
| Tool | Step code language | Maintained | Licence | Runs through | Reports |
|---|---|---|---|---|---|
| SpecFlow / Reqnroll | C# (Reqnroll) | Yes, community, sponsor funded | BSD 3-Clause | NUnit, MSTest, xUnit or TUnit | HTML and Cucumber Messages formatters |
| SpecFlow (end of life) | C# | No, ended 31 December 2024 | Open core; LivingDoc closed source | NUnit, MSTest or xUnit | SpecFlow+ LivingDoc, discontinued |
| Cucumber | Java, JavaScript, Ruby and others | Yes, community owned | MIT | JUnit Platform, CLI or npm script | HTML, JSON, JUnit XML, Cucumber Messages |
| Behave | Python | Yes, community | BSD 2-Clause | Its own behave command | JSON formatter and JUnit XML |
FAQ
Frequently asked questions
What are the best SpecFlow / Reqnroll alternatives?
The alternatives most often weighed against SpecFlow / Reqnroll are SpecFlow (end of life), Cucumber and Behave. The comparison on this page sets them side by side on step code language, maintained, licence, runs through and reports, and the verdict above says where SpecFlow / Reqnroll is the wrong choice and which of them to pick instead.
Is SpecFlow discontinued?
Yes. Tricentis announced the end of life of SpecFlow in December 2024, and it ended on 31 December 2024. The SpecFlow GitHub repositories were deleted and the support pages switched off; specflow.org now redirects to Tricentis’s community site. Packages remain on NuGet, so old projects still build, but SpecFlow works only up to .NET 7 and receives no fixes. Reqnroll is the maintained continuation.
What is Reqnroll?
Reqnroll is an open source .NET BDD framework created as a reboot of SpecFlow by Gáspár Nagy, SpecFlow’s creator and main contributor, with its first release in February 2024. It binds Gherkin feature files to C# step definitions, runs on NUnit, MSTest, xUnit or TUnit, supports .NET Framework 4.6.2 and later and modern .NET up to .NET 10, and is licensed under BSD 3-Clause. cucumber.io lists it as the semi-official .NET implementation.
How hard is it to migrate from SpecFlow to Reqnroll?
For most projects, small. Replace the SpecFlow packages with the matching Reqnroll ones and add Reqnroll.SpecFlowCompatibility, which keeps the TechTalk.SpecFlow namespace working; the official guide says this is often the only change. A complete migration also replaces namespaces, renames a few types such as ISpecFlowOutputHelper and moves specflow.json to reqnroll.json. Projects on SpecFlow v3 should also check the v3 to v4 breaking changes, mainly expression detection.
Is Reqnroll free?
Yes. Reqnroll is open source under the BSD 3-Clause licence, with no paid tiers. Its Visual Studio extension and Rider plugin are also free. The project is funded by sponsors listed on its website. The closed-source SpecFlow+ LivingDoc did not carry over; Reqnroll’s built-in HTML formatter covers the living documentation report, and the chosen test framework handles parallel execution.
What replaces SpecFlow+ LivingDoc?
The HTML formatter in Reqnroll 3, which writes a single-page report of features, scenarios and results using Cucumber’s report components; the documentation describes it as the default replacement for the SpecFlow+ LivingDoc Generator. Enable it in the formatters section of reqnroll.json or with the REQNROLL_FORMATTERS_HTML environment variable. The Message formatter also writes Cucumber Messages for tools that consume them.
Does Reqnroll support xUnit v3 and .NET 10?
Yes. Reqnroll 3.1 added xUnit v3 support through a separate Reqnroll.xunit.v3 package, and the project states support for .NET Framework 4.6.2 and later and modern .NET up to .NET 10. Release 3.3.4 in March 2026 updated the dotnet new templates for .NET 10 and xUnit v3. Reqnroll 3 dropped .NET 6 and .NET 7, which are out of Microsoft support.
Should a .NET team adopt BDD with Reqnroll?
Only if people outside the development team will help write and read the scenarios. Reqnroll is a good tool, but the Gherkin layer costs a feature file and bindings for every test. Where product owners take part in agreeing examples, that cost buys a shared, executable specification. Where they do not, plain NUnit, xUnit or MSTest tests are quicker to write and easier to debug.
Alternatives
SpecFlow / Reqnroll alternatives worth weighing
Resources
Where to go next
Picking the wrong tool costs more than picking late.
QAble advises on toolchain decisions and then implements them, with engineers who have run these tools on real delivery. Start with a free QA audit.