View all services
Talk to QA Advisor
Browse the Knowledge Hub211 resources
/Tools Library/SpecFlow / Reqnroll
Tools Library · Review and guide

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

Reviewed by Viral Patel

Co-Founder, QAbleLast reviewed 13 October 2026

At a glance

SpecFlow / Reqnroll at a glance

Official site
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

  1. Install the .NET SDK, and the Reqnroll extension for Visual Studio or the plugin for Rider.
  2. Install the Reqnroll project templates with dotnet new install.
  3. Create a project with dotnet new reqnroll-project, choosing the test framework with -t.
  4. Write a feature with one rule and one concrete example under the Features folder.
  5. Build and run dotnet test; copy the suggested snippets for undefined steps into a binding class.
  6. 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 test

A 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.html

Parallel 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

ToolStep code languageMaintainedLicenceRuns throughReports
SpecFlow / ReqnrollC# (Reqnroll)Yes, community, sponsor fundedBSD 3-ClauseNUnit, MSTest, xUnit or TUnitHTML and Cucumber Messages formatters
SpecFlow (end of life)C#No, ended 31 December 2024Open core; LivingDoc closed sourceNUnit, MSTest or xUnitSpecFlow+ LivingDoc, discontinued
CucumberJava, JavaScript, Ruby and othersYes, community ownedMITJUnit Platform, CLI or npm scriptHTML, JSON, JUnit XML, Cucumber Messages
BehavePythonYes, communityBSD 2-ClauseIts own behave commandJSON 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

Ready

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.

Talk to QA Advisor