SpecFlow Tutorial for .NET Test Automation
Learn how Gherkin scenarios become .NET automated tests, how to run them, and how to approach a SpecFlow-to-Reqnroll migration.
SpecFlow-style .NET test automation starts with a behavior written in Gherkin, connects each Given-When-Then step to C# code, and runs the resulting scenario through a .NET test framework and runner. For a new project today, use Reqnroll: the project describes itself as an open-source, Cucumber-style BDD framework and a reboot of SpecFlow. Follow its current quickstart for package IDs and versions, and its migration resources when updating an existing suite. The researched sources do not establish a definitive SpecFlow end-of-support date.
This tutorial explains the enduring workflow and gives a small scenario and step-definition example. Because package names, versions, and generation setup depend on the chosen test framework and current Reqnroll release, use the official quickstart for the exact executable project setup rather than copying an old SpecFlow package recipe.
1. Understand the BDD test flow
Behavior-driven development (BDD) makes a requirement concrete by describing an observable outcome in language that product, engineering, and testing can discuss. A Gherkin feature file then acts as an executable specification: each scenario is run as an automated test when its steps are bound to code.
- Describe behavior: Write a Feature and one or more Scenarios using Given, When, and Then.
- Bind the steps: Implement C# methods associated with the Gherkin step text.
- Exercise the system: The step code calls the application, API, or a test fixture and checks observable results.
- Run the test: The selected test framework and platform discover and execute the scenario.
Use Given for relevant starting context, When for an action, and Then for the outcome. Keep scenarios focused on behavior rather than implementation details such as private method names or database schema.
2. Write a small Gherkin feature
For example, suppose a shopping application should show a confirmation after a customer adds an item to a cart. Save a feature file such as Features/Cart.feature in the location configured by the current Reqnroll quickstart:
Feature: Shopping cart
A customer can add an available product to their cart.
Scenario: Add an available product
Given the catalog contains "Notebook" priced at 4.50
And the shopping cart is empty
When I add 2 "Notebook" items to the cart
Then the cart contains 2 "Notebook" items
And the cart total is 9.00
The prose under Feature gives context. The Scenario specifies one behavior and its expected result. A scenario should be deterministic: control its input data and avoid relying on a shared live service or mutable environment unless the test is specifically intended to cover that dependency.
3. Choose the test framework integration
Reqnroll’s overview lists MsTest, NUnit, and xUnit integrations. The test framework is the library and model used to define tests and assertions; the test platform is the runner and tooling that discovers and executes them. They are related, but they are not the same choice. Microsoft documents this distinction in its .NET testing overview.
| Decision | What to check |
|---|---|
| Test framework | Use the framework already used by the project where practical. Confirm Reqnroll’s current integration package and supported target frameworks in the official quickstart. |
| Test platform | Check the platform supported by the framework integration, your SDK, IDE, and CI runner. Keep platform configuration consistent across the solution. |
| IDE | Use the current Reqnroll editor guidance for your IDE. Its Marketplace listing names Visual Studio 2022 and 2026, VS Code, and Rider; verify current compatibility for your environment. |
For a basic project, start with the setup documented for your chosen Reqnroll integration and its default test platform. Current package identifiers and versions can change, so copy those from the official quickstart. Historical SpecFlow material used a package-per-test-framework model, but it is not a substitute for current Reqnroll setup instructions.
4. Implement step definitions in C#
A step definition binds Gherkin wording to a method. This illustrative binding uses Reqnroll-style attributes and a simple in-memory fixture to show the connection; adapt the fixture and assertion syntax to the integration and application under test. Confirm imports and package setup against the current Reqnroll documentation.
using System.Collections.Generic;
using System.Linq;
using Reqnroll;
using Xunit;
[Binding]
public sealed class CartSteps
{
private readonly Dictionary<string, decimal> _catalog = new();
private readonly Dictionary<string, int> _cart = new();
[Given("the catalog contains {string} priced at {decimal}")]
public void GivenCatalogContainsProduct(string name, decimal price)
{
_catalog[name] = price;
}
[Given("the shopping cart is empty")]
public void GivenShoppingCartIsEmpty()
{
_cart.Clear();
}
[When("I add {int} {string} items to the cart")]
public void WhenIAddItems(int quantity, string name)
{
if (!_catalog.ContainsKey(name))
throw new KeyNotFoundException($"Unknown product: {name}");
_cart[name] = _cart.GetValueOrDefault(name) + quantity;
}
[Then("the cart contains {int} {string} items")]
public void ThenCartContainsItems(int expected, string name)
{
Assert.Equal(expected, _cart.GetValueOrDefault(name));
}
[Then("the cart total is {decimal}")]
public void ThenCartTotalIs(decimal expected)
{
decimal total = _cart.Sum(item => _catalog[item.Key] * item.Value);
Assert.Equal(expected, total);
}
}
This snippet illustrates the binding idea; it is not a complete project by itself. The decimal expression syntax and attribute imports must match the Reqnroll version and integration chosen. If your selected integration uses a different assertion library, replace the illustrative xUnit assertion calls with its equivalent. Keep step methods small and reusable, and move shared scenario state into an appropriate scenario-scoped context or fixture rather than static fields.
Step-definition practices that scale
- Bind steps to domain actions and outcomes, not to each scenario’s entire sentence as a unique implementation.
- Use concrete, observable assertions in Then steps.
- Keep test data isolated per scenario so execution order does not change results.
- Use asynchronous step methods and hooks when the application-facing operations are asynchronous; Reqnroll documents async steps and hooks.
- Use regular expressions or Cucumber expressions according to the current framework documentation. Avoid overly broad bindings that make a phrase match several methods.
- Put browser, API, or database setup behind helper abstractions so feature files remain readable.
5. Restore, build, and run the scenarios
After creating the test project and adding the current Reqnroll integration packages from its quickstart, use the normal .NET CLI test workflow. Microsoft documents dotnet test as the command-line route for test projects; IDE test explorers are another way to discover and execute tests.
dotnet restore
dotnet build
dotnet test
Remove the leading space before dotnet build if copying this block into a shell; it is shown only for readability. A plain sequence without that space is:
dotnet restore
dotnet build
dotnet test
To run one project, pass its project file, for example dotnet test tests/Store.AcceptanceTests/Store.AcceptanceTests.csproj. To narrow execution, use the filtering options supported by the selected test framework and platform; check their official documentation for the exact filter syntax. Confirm in the output that the scenario was discovered, not merely that the build succeeded.
Keep local and CI execution consistent
- Pin or otherwise standardize the .NET SDK used by developers and CI.
- Restore and build the same test project in both environments.
- Run the same test platform and compatible framework integration.
- Provide required configuration and test data explicitly through CI settings or fixtures.
- Review test discovery and scenario results in CI; a successful compile alone does not prove that feature scenarios ran.
Microsoft distinguishes VSTest and Microsoft.Testing.Platform (MTP). Its guidance says not to mix VSTest-based and MTP-based test projects in one solution or run configuration. Native MTP mode for dotnet test requires the .NET 10 SDK or later according to the current comparison documentation. These details evolve; consult the platform comparison and MTP overview before changing a repository’s runner setup. For an introductory project, follow the default platform recommended by the selected framework integration.
6. Migrate an existing SpecFlow suite
Reqnroll provides a SpecFlow migration guide and describes compatibility and migration support. Use that guide as the current procedure; do not assume every legacy project migrates without changes.
- Record the existing target frameworks, SpecFlow packages, test framework, runner, IDE tooling, configuration files, hooks, and custom plugins.
- Read the migration guide for the current Reqnroll release and follow its package and configuration conversion steps.
- Restore packages and build before changing application behavior, so setup problems are visible independently.
- Check test discovery in the IDE and on the command line.
- Run representative scenarios, then the full suite. Review differences in generated code, hooks, plugins, and runner behavior.
- Update CI configuration to match the resulting framework and test-platform setup consistently across the solution.
A NuGet listing identifies a package and its published versions; by itself it does not establish a vendor’s maintenance status or support terms. The available researched material does not establish a definitive SpecFlow end-of-support date, so avoid inferring one from package presence or absence.
7. Troubleshooting
| Symptom | Likely cause | What to check or fix |
|---|---|---|
| Feature steps are not discovered as tests | Integration package, project configuration, feature generation, or runner setup is incomplete or mismatched. | Compare package IDs and setup with the current Reqnroll quickstart for the chosen framework. Clean and rebuild, then inspect test discovery output. |
| “No matching step definition” or an unbound step | Step wording, parameter type, expression, or binding scope does not match. | Compare the Gherkin text with the binding expression, verify parameter types and imports, and remove accidental ambiguity. Check the Reqnroll output for the unmatched step. |
| Duplicate step binding errors | Two methods match the same expression, perhaps in shared and project-specific bindings. | Search all binding classes, narrow expressions or scope where supported, and remove redundant definitions. |
| Build succeeds but IDE or CI reports no tests | The test adapter or platform is absent, incompatible, or configured differently between environments. | Verify the integration package and test platform combination, SDK version, and IDE/CI runner support. Compare discovery locally and in CI. |
| Runner behaves differently across projects | VSTest and MTP configuration has been mixed in a solution or run configuration. | Follow Microsoft’s platform consistency guidance and standardize the solution and CI setup. |
| Scenario passes alone but fails in a suite | Shared mutable state, order dependence, or incomplete cleanup in hooks or fixtures. | Isolate state per scenario, reset external data, and avoid static scenario state. |
| Migration compiles but scenarios fail | Configuration, plugin, hook, generated-code, or framework integration behavior changed. | Follow the migration guide and compare the failing scenario’s setup and execution path. Validate packages and runner compatibility for the target framework. |
8. Reliability, execution time, and maintenance
Scenario reliability mostly comes from controlling test inputs and dependencies. Prefer deterministic fixtures, isolate state, and make external service dependencies explicit. When an integration test needs a network service, define how test data is created and cleaned up, and avoid making its success depend on a developer’s personal environment.
Keep the suite useful by reserving end-to-end scenarios for important user-visible flows, while testing detailed edge cases at the layer that can cover them more quickly and consistently. Parallel execution can expose shared state; enable it only when fixtures and external resources are isolated. Measure your own suite before tuning concurrency because runtime depends on the application, runner, and environment.
Package and runner choices can affect supportability and CI behavior. Pin the SDK and dependencies in the repository’s normal way, review current framework integration guidance during upgrades, and keep all projects in a solution on a consistent test platform as Microsoft’s documentation directs.
9. Or skip the browser setup
If a test workflow needs a website screenshot as an artifact, you can capture it directly through ScreenshotNeo, a website screenshot API and MCP server for developers. See the API documentation for request options.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before capture; each cleanup step can be turned off. Bot checks, blank pages, and failed loads are never billed, and response headers say the page verdict and billing status. Its MCP server lets AI agents use screenshot tools. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots. Create a free account and get 1,000 screenshots a month with no card.
FAQ
Is SpecFlow the same as Reqnroll?
No. Reqnroll describes itself as a reboot of the SpecFlow project and provides migration guidance for existing suites. Treat migration as a project-specific change and verify it in your environment.
Can Gherkin scenarios replace unit tests?
They serve a different purpose: scenarios express behavior in a form stakeholders can discuss and exercise through automation. A .NET project can use them alongside unit and other integration tests.
Which test framework should I use?
Start with your team’s existing framework and dependencies, then verify current Reqnroll integration, IDE, CI, and target-framework compatibility. The Reqnroll overview lists MsTest, NUnit, and xUnit.
Does finding SpecFlow on NuGet prove it is supported?
No. A package listing shows package information; it does not by itself establish maintenance status or support terms.


