Skip to content

Integration ReqNRoll xUnit2

aryehcitron@gmail.com edited this page May 24, 2026 · 16 revisions

Example project: A complete working example is available at examples/Example.Api/tests/Example.Api.Tests.Component.ReqNRoll.xUnit2/.


Overview

This guide walks you through integrating Kronikol with ReqNRoll (the successor to SpecFlow) using xUnit v2 as the test runner. After completing this guide, your ReqNRoll BDD tests will automatically generate:

  • PlantUML sequence diagrams from HTTP traffic between your service and its dependencies
  • HTML reports with embedded diagrams and Gherkin steps (Given/When/Then/And/But)
  • YAML specification files with Gherkin steps included

xUnit v2 vs v3: This guide is for projects using xUnit v2. If you are using xUnit v3, see the Integration ReqNRoll xUnit3 guide instead. The main differences are the NuGet packages and the absence of <OutputType>Exe</OutputType> in this v2 guide.


Prerequisites

  • .NET 10.0 SDK or later
  • An ASP.NET Core API project to test (your "Service Under Test")
  • Basic familiarity with ReqNRoll / SpecFlow and Gherkin syntax

Step 1: Create the Test Project

Create a new xUnit test project:

dotnet new xunit -n MyApi.Tests.Component.ReqNRoll

Your .csproj <PropertyGroup> should look like this (note: no <OutputType>Exe</OutputType> — that is only for xUnit v3):

<PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <IsPackable>false</IsPackable>
    <IsTestProject>true</IsTestProject>
</PropertyGroup>

Step 2: Install NuGet Packages

Add the following packages to your test project:

dotnet add package Kronikol.ReqNRoll.xUnit2
dotnet add package Reqnroll --version 3.3.4
dotnet add package Reqnroll.xUnit --version 3.3.4
dotnet add package xunit --version 2.9.3
dotnet add package xunit.runner.visualstudio --version 3.1.5
dotnet add package Microsoft.AspNetCore.Mvc.Testing
dotnet add package Microsoft.NET.Test.Sdk

Your <ItemGroup> should look like this:

<ItemGroup>
    <PackageReference Include="Kronikol.ReqNRoll.xUnit2" Version="2.31.0" />
    <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.5" />
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.3.0" />
    <PackageReference Include="Reqnroll" Version="3.3.4" />
    <PackageReference Include="Reqnroll.xUnit" Version="3.3.4" />
    <PackageReference Include="xunit" Version="2.9.3" />
    <PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
        <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
        <PrivateAssets>all</PrivateAssets>
    </PackageReference>
</ItemGroup>

Note: Reqnroll.xUnit is the xUnit v2 runner. Do not use Reqnroll.xunit.v3 — that is for xUnit v3.


Step 3: Add the reqnroll.json Configuration File (Critical)

Create a reqnroll.json file in your test project root. This step is mandatory — without it, the library's hooks will not be discovered and all tests will fail with InvalidOperationException: No ReqNRoll scenario is currently executing.

{
  "$schema": "https://schemas.reqnroll.net/reqnroll-config-latest.json",
  "bindingAssemblies": [
    {
      "assembly": "Kronikol.ReqNRoll.xUnit2"
    }
  ],
  "formatters": {
    "html": { "outputFilePath": "reqnroll_report.html" }
  }
}

Why is this needed? ReqNRoll only auto-discovers [Binding] classes in the test project's own assembly. The library's hooks (ReqNRollTrackingHooks, ReqNRollTestRunHooks) live in the Kronikol.ReqNRoll.xUnit2 assembly, so you must explicitly register it.

The formatters.html section enables ReqNRoll's native HTML report. When enabled, the library additionally enhances the report with sequence diagram images attached to each scenario — on top of the standard custom reports that are always generated in the Reports directory. See ReqNRoll Report Enhancement below.


Step 4: Create the Test Setup Hooks

Create a Hooks/TestSetupHooks.cs file. This class:

  1. Creates a WebApplicationFactory for your API and registers diagram tracking
  2. Provides each scenario with a tracking-enabled HttpClient
  3. Generates the reports after all tests complete
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.TestHost;
using Reqnroll;
using Reqnroll.BoDi;
using Kronikol;
using Kronikol.ReqNRoll.xUnit2;

namespace MyApi.Tests.Component.ReqNRoll.Hooks;

[Binding]
public class TestSetupHooks
{
    private const string ServiceUnderTestName = "My API";

    private static WebApplicationFactory<Program>? _factory;

    [BeforeTestRun]
    public static void BeforeTestRun()
    {
        _factory = new WebApplicationFactory<Program>().WithWebHostBuilder(builder =>
        {
            builder.ConfigureTestServices(services =>
            {
                // Register diagram tracking for all outgoing HTTP calls
                services.TrackDependenciesForDiagrams(new ReqNRollTestTrackingMessageHandlerOptions
                {
                    CallerName = ServiceUnderTestName,
                    // Map ports to friendly service names for diagram labels
                    PortsToServiceNames =
                    {
                        { 80, ServiceUnderTestName },
                        { 5001, "Downstream Service A" },
                        { 5002, "Downstream Service B" }
                    }
                });
            });
        });
    }

    [BeforeScenario]
    public void BeforeScenario(IObjectContainer objectContainer)
    {
        // Create a tracking-enabled HttpClient and register it for injection
        var client = _factory!.CreateTestTrackingClient(
            new ReqNRollTestTrackingMessageHandlerOptions
            {
                FixedNameForReceivingService = ServiceUnderTestName
            });
        objectContainer.RegisterInstanceAs(client);
    }

    [AfterScenario]
    public void AfterScenario(IObjectContainer objectContainer)
    {
        var client = objectContainer.Resolve<HttpClient>();
        client.Dispose();
    }

    [AfterTestRun]
    public static void AfterTestRun()
    {
        // Generate the HTML and YAML reports with diagrams
        ReqNRollReportGenerator.CreateStandardReportsWithDiagrams(
            new ReportConfigurationOptions
            {
                SpecificationsTitle = "My API Specifications"
            });

        _factory?.Dispose();
    }
}

What each hook does:

Hook Purpose
[BeforeTestRun] Creates the WebApplicationFactory and registers HTTP tracking
[BeforeScenario] Creates a fresh tracking HttpClient for each scenario and injects it via IObjectContainer
[AfterScenario] Disposes the HttpClient
[AfterTestRun] Generates all reports (HTML + YAML) with embedded sequence diagrams

Step 5: Write a Gherkin Feature File

Create a .feature file under a Features/ directory, e.g. Features/Cake.feature:

@endpoint:/cake
Feature: Cake
    As a dessert provider
    I want to create cakes from ingredients
    So that customers can enjoy delicious cakes

@happy-path
Scenario: Calling Create Cake Endpoint Successfully
    Given a valid post request for the Cake endpoint
    When the request is sent to the cake post endpoint
    Then the response should be successful

Scenario: Calling Create Cake Endpoint Without Eggs
    Given a valid post request for the Cake endpoint
    But the request body is missing eggs
    When the request is sent to the cake post endpoint
    Then the response http status should be bad request

Tags that Kronikol uses:

Tag Purpose
@endpoint:/cake Sets the endpoint label on the feature in the report
@happy-path Marks the scenario as a "happy path" (highlighted in reports, filterable)

Step 6: Write Step Definitions

Create a StepDefinitions/CakeStepDefinitions.cs. Note that the HttpClient is constructor-injected by ReqNRoll's DI container (registered by TestSetupHooks.BeforeScenario):

using System.Net;
using System.Net.Http.Json;
using Reqnroll;

namespace MyApi.Tests.Component.ReqNRoll.StepDefinitions;

[Binding]
public class CakeStepDefinitions
{
    private readonly HttpClient _client;
    private HttpResponseMessage? _response;

    public CakeStepDefinitions(HttpClient client)
    {
        _client = client;
    }

    [Given("a valid post request for the Cake endpoint")]
    public async Task GivenAValidPostRequestForTheCakeEndpoint()
    {
        // Set up your request data
    }

    [Given("the request body is missing eggs")]
    public void GivenTheRequestBodyIsMissingEggs()
    {
        // Modify request to remove eggs
    }

    [When("the request is sent to the cake post endpoint")]
    public async Task WhenTheRequestIsSentToTheCakePostEndpoint()
    {
        _response = await _client.PostAsJsonAsync("cake", /* your request */);
    }

    [Then("the response should be successful")]
    public async Task ThenTheResponseShouldBeSuccessful()
    {
        _response!.StatusCode.Should().Be(HttpStatusCode.OK);
    }

    [Then("the response http status should be bad request")]
    public void ThenTheResponseHttpStatusShouldBeBadRequest()
    {
        _response!.StatusCode.Should().Be(HttpStatusCode.BadRequest);
    }
}

Step 7: Run the Tests

dotnet test

After the tests complete, check the bin/Debug/net10.0/Reports/ folder. You should find three files:

File Description
Specifications.html HTML specifications with embedded PlantUML sequence diagrams
TestRunReport.html HTML test run report with diagrams and execution summary
Specifications.yml YAML specifications with Gherkin steps

Report Features (ReqNRoll-specific)

The ReqNRoll adapter produces richer reports than the XUnit/NUnit adapters because it has access to Gherkin metadata:

  • Gherkin steps — Each scenario in the HTML report shows its Given/When/Then/And/But steps with keyword highlighting
  • Feature descriptions — The feature description from the .feature file is shown under the feature heading
  • YAML with steps — The YAML spec includes a Steps: array per scenario with the full Gherkin steps

Customisation Options

ReportConfigurationOptions

Pass these when calling ReqNRollReportGenerator.CreateStandardReportsWithDiagrams:

Property Default Description
SpecificationsTitle "Service Specifications" Title shown at the top of reports
PlantUmlServerBaseUrl "https://plantuml.com/plantuml" PlantUML server URL for diagram rendering
HtmlSpecificationsFileName "Specifications" Output filename for the specifications HTML
HtmlTestRunReportFileName "TestRunReport" Output filename for the test run HTML
YamlSpecificationsFileName "Specifications" Output filename for the YAML specs
HtmlSpecificationsCustomStyleSheet Stylesheets.VioletThemeStyleSheet Custom CSS to append to the specifications HTML
ExcludedHeaders [] HTTP headers to exclude from diagrams
RequestResponsePostProcessor null Post-processing function for request/response content in diagrams
SeparateSetup false When true, HTTP calls made during GIVEN steps are wrapped in a visual "Setup" partition in the diagram
HighlightSetup true When true (and SeparateSetup is enabled), the setup partition is rendered with a background colour

ReqNRollTestTrackingMessageHandlerOptions

Pass these when calling TrackDependenciesForDiagrams and CreateTestTrackingClient:

Property Description
CallerName Display name for the service making outgoing HTTP calls
FixedNameForReceivingService Display name for the service receiving requests (your SUT)
PortsToServiceNames Dictionary mapping port numbers to friendly service names. Unmapped ports appear as localhost_80, localhost_5001, etc.

Setup separation: When SeparateSetup = true, ReqNRoll automatically detects the boundary between GIVEN steps and WHEN/THEN steps. No manual StartAction() call is needed.


ReqNRoll Report Enhancement

Note: The CUCUMBER_MESSAGES-based report enhancement mechanism (ReqNRollReportEnhancer) described in earlier versions of this documentation has been removed from the codebase. The ReqNRollReportEnhancer class still exists in the API surface for backward compatibility, but the CUCUMBER_MESSAGES injection logic is no longer present.

The standard custom reports (HTML specifications, HTML test run report, YAML specs) are always generated in the Reports directory. When ReqNRoll's built-in HTML formatter is additionally enabled (via the formatters.html section in reqnroll.json), it will produce its own native report, but diagram injection into that native report is not currently functional.

How to enable the native ReqNRoll report (without diagram enhancement)

Add the formatters section to your reqnroll.json:

{
  "formatters": {
    "html": { "outputFilePath": "reqnroll_report.html" }
  }
}

After tests complete, the native ReqNRoll report will be at bin/Debug/net10.0/reqnroll_report.html, but it will not contain sequence diagram attachments. Use the Kronikol-generated reports in the Reports/ directory for diagrams.


Faking Downstream Dependencies (Correctly)

When your SUT calls downstream HTTP services, those calls must flow through TestTrackingMessageHandler to produce proper HTTP-style diagram arrows (with method, status code, headers, body). Do not mock service client interfaces and use MessageTracker to manually log HTTP interactions — this produces event-style (blue) arrows that are misleading.

Recommended approaches:

See Tracking Dependencies#faking-dependencies-getting-proper-http-tracking for detailed examples of each approach.


Advanced Gherkin Features

Category Tags

Use @category: tags to group scenarios into named categories. Categories appear in generated reports and can be used for filtering:

@category:payments
@category:critical
Feature: Payment Processing

Multiple @category: tags can be applied to a single feature or scenario. The prefix is case-insensitive.

Rule Blocks

Gherkin Rule blocks are fully supported. The rule title is captured and included in the generated report data:

Feature: Cake Ordering

  Rule: Cakes must contain at least one ingredient

    Scenario: Order cake with ingredients
      Given a cake with flour and sugar
      When I place the order
      Then the order succeeds

Scenario Outlines

Scenario Outlines with Examples tables are tracked automatically. Each example row produces a separate scenario in the report, with the example values captured:

Scenario Outline: Creating a <flavour> cake
  Given a request for a <flavour> cake with <layers> layers
  When the request is sent
  Then the response contains a <flavour> cake

  Examples:
    | flavour   | layers |
    | chocolate  | 3      |
    | vanilla    | 2      |

The OutlineId (the scenario title template) and ExampleValues dictionary (column→value) are captured and available in the report data.

Step Tables

Data tables attached to steps are captured as TableText and included in the report:

Scenario: Create cake with specific ingredients
  Given the following ingredients:
    | Ingredient | Amount |
    | Flour      | 200g   |
    | Sugar      | 100g   |
  When the cake is baked
  Then it should be delicious

DocStrings

Multi-line DocStrings on steps are captured and available in the report data:

Scenario: Create cake from recipe
  Given the following recipe:
    """json
    {
      "name": "Chocolate Cake",
      "ingredients": ["flour", "cocoa", "sugar"]
    }
    """
  When the cake is baked
  Then it should match the recipe

The DocStringMediaType field exists on the ScenarioStep record but is not currently populated automatically by the ReqNRoll adapter. It will always be null.


Troubleshooting

"No ReqNRoll scenario is currently executing"

You are missing the reqnroll.json file, or it does not list Kronikol.ReqNRoll.xUnit2 in bindingAssemblies. See Step 3.

Reports folder is empty

  • Ensure [AfterTestRun] calls ReqNRollReportGenerator.CreateStandardReportsWithDiagrams
  • If any test has failed, the specifications HTML and YAML files will be blank (by design — they only generate on fully passing runs). The TestRunReport.html will still be generated regardless.

And/But keywords show as Given/When/Then

Ensure you are using Kronikol.ReqNRoll.xUnit2 version 1.20.0 or later. Earlier versions used StepDefinitionType (which collapses keywords) instead of StepDefinitionKeyword.

Migrating from xUnit v2 to xUnit v3

If you later want to migrate to xUnit v3, see the Integration ReqNRoll xUnit3 guide. The key changes are:

  1. Replace Reqnroll.xUnit with Reqnroll.xunit.v3
  2. Replace xunit with xunit.v3
  3. Add <OutputType>Exe</OutputType> to your csproj
  4. Update reqnroll.json to reference Kronikol.ReqNRoll.xUnit3
  5. Change your using statements from Kronikol.ReqNRoll.xUnit2 to Kronikol.ReqNRoll.xUnit3
  6. Clean bin/ and obj/ directories before building

Home


Demo


Getting Started

Common Tasks

Integration Guides

Extensions

Configuration

Features

Reference

Clone this wiki locally