reqnroll-testing

Use when a .NET project starts BDD, migrates from SpecFlow (end-of-life 2024-12-31) or maintains a legacy SpecFlow suite. Configures Reqnroll: Gherkin .feature files, [Given/When/Then] C# bindings, hooks and dotnet test runs.

/plugin install qa-bdd@testland-qa

First time in Claude Code? Add the marketplace with /plugin marketplace add testland/qa

View source (opens in a new tab)

Overview

Per reqnroll-home (opens in new window):

"Reqnroll is described as 'an open-source Cucumber-style BDD test automation framework for .NET. It has been created as a reboot of the SpecFlow project.'"

The "reboot" framing is the key: SpecFlow's maintenance slowed in 2023; the community forked into Reqnroll, which has continued active development.

When to use

  • A new .NET project starts BDD - pick Reqnroll over SpecFlow.
  • An existing SpecFlow project plans to migrate (SpecFlow-compatible; see Step 9).
  • The team needs full Gherkin support including Rule blocks (per reqnroll-home (opens in new window): "Full Gherkin support with tagged Rule blocks").

For SpecFlow-locked legacy projects mid-migration (packages, bindings, EOL background), see references/specflow-legacy.md.

Worked example

A checkout team adds one BDD scenario, "Apply valid promo," to a new xUnit project.

  1. dotnet add package Reqnroll.xUnit and dotnet add package Reqnroll.Tools.MsBuild.Generation.
  2. Features/Cart.feature declares the scenario under Rule: Promo codes apply only when valid, with When I enter "WELCOME10" in the promo input / Then the subtotal updates to $22.49.
  3. Steps/CartSteps.cs binds each line - [When(@"I enter ""([^""]*)"" in the promo input")] calls _page.EnterPromoAsync(code), and the Then asserts Assert.Equal(22.49m, _page.GetSubtotal(), 2).
  4. dotnet test --filter "FullyQualifiedName~Cart" runs only that feature.
  5. Result: the scenario turns green, and the MSBuild generator has produced the runnable test class from the .feature file - no glue code beyond the bindings.

Step 1 - Install

# In the test project directory
dotnet add package Reqnroll.xUnit                    # or Reqnroll.NUnit / Reqnroll.MsTest
dotnet add package Reqnroll.Tools.MsBuild.Generation  # generates code from .feature files

Per reqnroll-home (opens in new window): "Works across common operating systems and .NET versions (including .NET 8.0)."

Verify: dotnet build succeeds and restores both packages before you author features. If code generation does not run, confirm Reqnroll.Tools.MsBuild.Generation is referenced in the test project.

Step 2 - Author a Feature

# Features/Cart.feature
Feature: Apply promo code at checkout

  Background:
    Given a logged-in user with email confirmed
    And the cart contains 1 of "BOOK-001" at $24.99

  Rule: Promo codes apply only when valid

    Scenario: Apply valid promo
      When I enter "WELCOME10" in the promo input
      And I click "Apply"
      Then the subtotal updates to $22.49

    Scenario Outline: Reject invalid codes
      When I enter "<code>" in the promo input
      And I click "Apply"
      Then an error appears: "<error>"

      Examples:
        | code       | error                 |
        | EXPIRED50  | This code has expired |
        | NOTREAL    | Code not found        |

Rule: blocks (Gherkin 6+) group related scenarios; per reqnroll-home (opens in new window) this is supported.

Step 3 - Step bindings

Write step bindings as [Given/When/Then]-decorated methods in a [Binding] class. Per reqnroll-home (opens in new window): "Supports flexible step definitions using regex or cucumber expressions" - regex is more flexible, cucumber expressions ({int}, {string}, {double}) more readable. Full CartSteps binding class and the cucumber-expression variant: references/bindings-and-hooks.md.

Step 4 - Async support

Per reqnroll-home (opens in new window): "Async step definitions and hooks."

[Then("the order arrives within (\\d+) minutes")]
public async Task ThenOrderArrives(int minutes)
{
    await EmailInbox.WaitForOrderConfirmation(TimeSpan.FromMinutes(minutes));
}

async Task step methods work transparently; no special config.

Step 5 - Hooks

Use [BeforeTestRun], [BeforeScenario] / [AfterScenario], and tag-scoped hooks (e.g. [BeforeScenario("@browser")]) for setup and teardown; wrap each scenario in a transaction and roll back in [AfterScenario] so state does not leak between scenarios. Full hook class: references/bindings-and-hooks.md.

Step 6 - Tags

@critical @regression
Scenario: Apply valid promo
  ...

@browser @wip
Scenario: New checkout flow
  ...
dotnet test --filter "Category=critical"
dotnet test --filter "Category!=wip"

Step 7 - Run

# All tests
dotnet test

# Specific feature
dotnet test --filter "FullyQualifiedName~Cart"

# Generate JUnit XML for CI
dotnet test --logger "junit;LogFilePath=reports/test-results.xml"

If a scenario fails or reports an undefined step, fix the matching binding in the [Binding] class and re-run until it turns green.

Step 8 - IDE support

Per reqnroll-home (opens in new window): "IDE support for Visual Studio 2022, VS Code, and Rider."

The Reqnroll plugin enables:

  • Step navigation (Gherkin → step binding).
  • Scaffolding (auto-generate step from undefined Gherkin line).
  • Run/debug per-scenario.

Step 9 - Migrate from SpecFlow

Per reqnroll-home (opens in new window): "Compatible with SpecFlow, allowing quick migration of existing projects."

Migration path:

  1. Replace SpecFlow.* NuGet packages with Reqnroll.* equivalents.
  2. Update using SpecFlow → using Reqnroll (single find/replace).
  3. Update [Binding] (compatible) and step decorators (mostly compatible).
  4. Run tests; fix any compatibility issues.

Most SpecFlow projects migrate in <1 day for typical scope.

Anti-patterns

Anti-patternWhy it failsFix
Starting new .NET BDD with SpecFlow in 2026+SpecFlow's maintenance has slowed; Reqnroll is the active fork.Pick Reqnroll (Step 1).
Mixing SpecFlow + Reqnroll in one solutionTwo BDD runners; double maintenance.Migrate everything (Step 9).
Regex-only steps when cucumber expressions would workLess readable; harder to maintain.Cucumber expressions for typical cases (Step 3).
No [BeforeScenario] cleanupState leaks between scenarios.Per-scenario hook + transaction rollback (Step 5).
Sync step methods that block on asyncDeadlocks in xUnit / NUnit / MsTest.async Task step methods (Step 4).

Limitations

  • Newer than SpecFlow. Some third-party SpecFlow plugins haven't been ported yet.
  • Per-test-framework variant. xUnit / NUnit / MsTest each have their own Reqnroll package; pin matching versions.
  • Gherkin parser quirks. Some edge cases differ between Reqnroll and Cucumber-JVM; cross-port scenarios need verification.

References

  • rh (opens in new window) - Reqnroll overview: SpecFlow reboot, Gherkin Rule blocks, SpecFlow-compatible migration, async hooks, IDE support (VS / VS Code / Rider).
  • references/bindings-and-hooks.md - full step-binding class, cucumber-expression variant, and hook class.
  • references/specflow-legacy.md - legacy SpecFlow support + EOL background + full migration path.
  • cucumber-testing, behave-testing - sibling language wrappers.
  • bdd-step-library-curator - addresses step proliferation.
Reference

Reqnroll step bindings and hooks

View source (opens in a new tab) (opens in new window)

Full step-binding and hook examples for reqnroll-testing (opens in new window) (Steps 3 and 5).

Step bindings

// Steps/CartSteps.cs
using Reqnroll;
using Xunit;

[Binding]
public class CartSteps
{
    private CheckoutPage _page;
    private Cart _cart;

    [Given("a logged-in user with email confirmed")]
    public async Task GivenLoggedInUser()
    {
        var user = await TestUsers.LoggedInWithEmailConfirmed();
        _page = new CheckoutPage(user);
    }

    [Given(@"the cart contains (\d+) of ""([^""]*)"" at \$(\d+\.\d+)")]
    public void GivenCartContains(int qty, string sku, decimal price)
    {
        _cart = new Cart();
        _cart.AddItem(new Item(sku, qty, price));
        _page.SetCart(_cart);
    }

    [When(@"I enter ""([^""]*)"" in the promo input")]
    public async Task WhenIEnter(string code)
    {
        await _page.EnterPromoAsync(code);
    }

    [When(@"I click ""([^""]*)""")]
    public async Task WhenIClick(string label)
    {
        await _page.ClickAsync(label);
    }

    [Then(@"the subtotal updates to \$(\d+\.\d+)")]
    public void ThenSubtotalUpdates(decimal expected)
    {
        Assert.Equal(expected, _page.GetSubtotal(), 2);
    }
}

Per reqnroll-home (opens in new window): "Supports flexible step definitions using regex or cucumber expressions." The example uses regex; cucumber expressions are an alternative:

[Given("the cart contains {int} of {string} at ${double}")]
public void GivenCartContains(int qty, string sku, double price) { ... }

Cucumber expressions are more readable; regex is more flexible.

Hooks

using Reqnroll;

[Binding]
public class TestHooks
{
    [BeforeTestRun]
    public static async Task BeforeTestRun()
    {
        // Once per test run
        await TestDatabase.Initialize();
    }

    [BeforeScenario]
    public async Task BeforeScenario()
    {
        // Per-scenario
        await TestDatabase.StartTransaction();
    }

    [AfterScenario]
    public async Task AfterScenario(ScenarioContext context)
    {
        await TestDatabase.Rollback();
        if (context.TestError is not null)
        {
            await ScreenshotCapture.Capture(context.ScenarioInfo.Title);
        }
    }

    [BeforeScenario("@browser")]
    public async Task BeforeBrowserScenario()
    {
        // Tag-scoped hook
        await Browser.LaunchAsync();
    }
}

Source

Reference

SpecFlow legacy support and migration to Reqnroll

View source (opens in a new tab) (opens in new window)

Deep reference for reqnroll-testing. Consult when maintaining an existing SpecFlow project that has not migrated yet, or when executing the migration.

SpecFlow is end-of-life

SpecFlow was the standard .NET BDD runner for a decade. It is dead: Tricentis, which owned it, states "SpecFlow has been retired" (shiftsync.tricentis.com (opens in new window)), specflow.org redirects there, and it "reached its end-of-life on December 31, 2024" with the GitHub projects deleted as of 1 January (reqnroll.net (opens in new window)). The packages still install only because nuget.org will not delete existing ones - exactly how newcomers land on an unsupported dependency. New .NET BDD work targets Reqnroll; use this page only for existing SpecFlow projects, especially mid-migration.

Maintaining an existing SpecFlow project (legacy)

Package references (legacy):

<PackageReference Include="SpecFlow" Version="3.9.74" />
<PackageReference Include="SpecFlow.xUnit" Version="3.9.74" />
<PackageReference Include="SpecFlow.Tools.MsBuild.Generation" Version="3.9.74" />

Features are the same Gherkin as Reqnroll / Cucumber. Step bindings:

using TechTalk.SpecFlow;
using Xunit;

[Binding]
public class CartSteps
{
    [Given("a logged-in user")]
    public void GivenLoggedInUser() { /* ... */ }

    [When(@"I enter ""([^""]*)"" in the promo input")]
    public void WhenIEnter(string code) { /* ... */ }

    [Then(@"the subtotal updates to \$(\d+\.\d+)")]
    public void ThenSubtotalUpdates(decimal expected) { /* ... */ }
}

Compare to Reqnroll: using TechTalk.SpecFlow → using Reqnroll; decorators identical. Running is the same dotnet test (the runner is the .NET test framework - xUnit / NUnit / MsTest).

Migration path

Per reqnroll.net (opens in new window): "Compatible with SpecFlow, allowing quick migration of existing projects."

# 1. Remove SpecFlow packages
dotnet remove package SpecFlow
dotnet remove package SpecFlow.xUnit
dotnet remove package SpecFlow.Tools.MsBuild.Generation

# 2. Add Reqnroll equivalents
dotnet add package Reqnroll.xUnit
dotnet add package Reqnroll.Tools.MsBuild.Generation
// 3. Find/replace in code:
//   using TechTalk.SpecFlow → using Reqnroll
//   TechTalk.SpecFlow → Reqnroll
# 4. Run tests; fix any breakages
dotnet test

Most projects migrate in under a day; the migration is mostly mechanical.

Anti-patterns

Anti-patternWhy it failsFix
Starting new .NET BDD with SpecFlowReqnroll is the actively-maintained successor; SpecFlow is EOLUse the main reqnroll-testing skill
Postponing migration indefinitelySpecFlow falls further behind .NET / IDE supportMigrate now; the cost grows over time
Mixing SpecFlow + Reqnroll in one solutionTwo runners; conflictsAll-or-nothing migration

Limitations of staying on SpecFlow

  • Maintenance status. EOL - no bug fixes, no new features.
  • .NET version compatibility. Newer .NET versions land on Reqnroll only.
  • IDE plugin updates. SpecFlow plugins for VS / Rider are frozen; Reqnroll's are maintained.