PQSoft.ReqNRoll 1.2.0-beta.4

This is a prerelease version of PQSoft.ReqNRoll.
dotnet add package PQSoft.ReqNRoll --version 1.2.0-beta.4
                    
NuGet\Install-Package PQSoft.ReqNRoll -Version 1.2.0-beta.4
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="PQSoft.ReqNRoll" Version="1.2.0-beta.4" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="PQSoft.ReqNRoll" Version="1.2.0-beta.4" />
                    
Directory.Packages.props
<PackageReference Include="PQSoft.ReqNRoll" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add PQSoft.ReqNRoll --version 1.2.0-beta.4
                    
#r "nuget: PQSoft.ReqNRoll, 1.2.0-beta.4"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package PQSoft.ReqNRoll@1.2.0-beta.4
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=PQSoft.ReqNRoll&version=1.2.0-beta.4&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=PQSoft.ReqNRoll&version=1.2.0-beta.4&prerelease
                    
Install as a Cake Tool

PQSoft.ReqNRoll

Write API tests in plain English using Gherkin syntax. No boilerplate, no manual HTTP client setup, no JSON parsing headaches.

Why Use This?

Before PQSoft.ReqNRoll:

[Fact]
public async Task CreateJob_ReturnsJobId()
{
    var client = _factory.CreateClient();
    var content = new StringContent("{\"JobType\":\"Upgrade\"}", Encoding.UTF8, "application/json");
    var response = await client.PostAsync("/api/job", content);
    
    response.StatusCode.Should().Be(HttpStatusCode.Created);
    var body = await response.Content.ReadAsStringAsync();
    var json = JsonDocument.Parse(body);
    var jobId = json.RootElement.GetProperty("jobId").GetString();
    jobId.Should().NotBeNullOrEmpty();
    
    // Now use that jobId in another request...
    var getResponse = await client.GetAsync($"/api/job/status/{jobId}");
    // More parsing, more assertions...
}

After PQSoft.ReqNRoll:

Scenario: Create a new job
  Given the following request
  """
  POST /api/job HTTP/1.1
  Content-Type: application/json
  
  {
    "JobType": "Upgrade"
  }
  """
  
  Then the API returns the following response
  """
  HTTP/1.1 201 Created
  Content-Type: application/json
  
  {
    "jobId": [[JOBID]]
  }
  """
  
  Given the following request
  """
  GET /api/job/status/{{JOBID}} HTTP/1.1
  """
  
  Then the API returns the following response
  """
  HTTP/1.1 200 OK
  
  {
    "jobId": "{{JOBID}}",
    "status": "Pending"
  }
  """

Installation

dotnet add package PQSoft.ReqNRoll
dotnet add package Reqnroll.xUnit  # or Reqnroll.NUnit
dotnet add package Microsoft.AspNetCore.Mvc.Testing

Quick Start

1. Create a Step Definition Class

using Microsoft.AspNetCore.Mvc.Testing;
using PQSoft.ReqNRoll;
using Reqnroll;

[Binding]
public class ApiSteps : ApiStepDefinitions
{
    public ApiSteps(WebApplicationFactory<Program> factory) 
        : base(factory.CreateClient()) 
    {
    }
}

2. Write Your Feature File

Feature: User API

Scenario: Create and retrieve a user
  Given the following request
  """
  POST /api/users HTTP/1.1
  Content-Type: application/json
  
  {
    "name": "Alice",
    "email": "alice@example.com"
  }
  """
  
  Then the API returns the following response
  """
  HTTP/1.1 201 Created
  Content-Type: application/json
  
  {
    "id": [[USER_ID]],
    "name": "Alice",
    "email": "alice@example.com"
  }
  """

That's it. No manual HTTP setup, no JSON parsing, no assertion boilerplate.

Features

Token Extraction

Extract values from responses and use them in subsequent requests:

# Extract with [[TOKEN_NAME]]
Then the API returns the following response
"""
HTTP/1.1 200 OK

{
  "orderId": [[ORDER_ID]],
  "userId": [[USER_ID]]
}
"""

# Use with {{TOKEN_NAME}}
Given the following request
"""
GET /api/orders/{{ORDER_ID}}/user/{{USER_ID}} HTTP/1.1
"""

Subset Matching

Only specify the fields you care about:

Then the API returns the following response
"""
HTTP/1.1 200 OK

{
  "status": "active"
}
"""

This matches even if the actual response has 50 other fields. Perfect for testing specific behaviors without brittle tests.

Header Validation

Headers are automatically validated:

Then the API returns the following response
"""
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
X-Request-Id: [[REQUEST_ID]]

{ "data": "..." }
"""

Variable Assertions

Then the variable 'USER_ID' is of type 'String'
Then the variable 'CREATED_AT' is of type 'Date'
Then the variable 'COUNT' is of type 'Number'
Then the variable 'IS_ACTIVE' is of type 'Boolean'

Then the variable 'EMAIL' is equals to 'test@example.com'
Then the variable 'ORDER_ID' matches '^ORD-\d{6}$'

Real-World Examples

Testing Error Responses

Scenario: Invalid input returns 400
  Given the following request
  """
  POST /api/job HTTP/1.1
  Content-Type: application/json
  
  {
    "JobType": ""
  }
  """
  
  Then the API returns the following response
  """
  HTTP/1.1 400 BadRequest
  Content-Type: application/problem+json
  
  {
    "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
    "title": "Job Type Invalid",
    "status": 400,
    "detail": "The Job Type '' is invalid."
  }
  """

Testing 404s

Scenario: Non-existent resource returns 404
  Given the following request
  """
  GET /api/job/status/DOES_NOT_EXIST HTTP/1.1
  """
  
  Then the API returns the following response
  """
  HTTP/1.1 404 NotFound
  Content-Type: application/problem+json
  
  {
    "title": "Job Not Found",
    "status": 404
  }
  """

Multi-Step Workflows

Scenario: Complete order workflow
  # Create user
  Given the following request
  """
  POST /api/users HTTP/1.1
  Content-Type: application/json
  
  { "name": "Bob" }
  """
  Then the API returns the following response
  """
  HTTP/1.1 201 Created
  { "id": [[USER_ID]] }
  """
  
  # Create order
  Given the following request
  """
  POST /api/orders HTTP/1.1
  Content-Type: application/json
  
  { "userId": "{{USER_ID}}", "item": "Widget" }
  """
  Then the API returns the following response
  """
  HTTP/1.1 201 Created
  { "orderId": [[ORDER_ID]], "status": "pending" }
  """
  
  # Process order
  Given the following request
  """
  POST /api/orders/{{ORDER_ID}}/process HTTP/1.1
  """
  Then the API returns the following response
  """
  HTTP/1.1 200 OK
  { "orderId": "{{ORDER_ID}}", "status": "completed" }
  """
  
  # Verify final state
  Given the following request
  """
  GET /api/users/{{USER_ID}}/orders HTTP/1.1
  """
  Then the API returns the following response
  """
  HTTP/1.1 200 OK
  {
    "orders": [
      { "orderId": "{{ORDER_ID}}", "status": "completed" }
    ]
  }
  """

Testing with Authentication

Scenario: Authenticated request
  Given the following request
  """
  GET /api/profile HTTP/1.1
  Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
  """
  
  Then the API returns the following response
  """
  HTTP/1.1 200 OK
  
  {
    "username": "alice",
    "role": "admin"
  }
  """

Advanced Usage

Custom HttpClient Configuration

[Binding]
public class ApiSteps : ApiStepDefinitions
{
    public ApiSteps(WebApplicationFactory<Program> factory) 
        : base(CreateConfiguredClient(factory)) 
    {
    }
    
    private static HttpClient CreateConfiguredClient(WebApplicationFactory<Program> factory)
    {
        var client = factory.CreateClient();
        client.DefaultRequestHeaders.Add("X-API-Version", "2.0");
        client.Timeout = TimeSpan.FromSeconds(30);
        return client;
    }
}

Adding Custom Steps

Extend the base class with your own steps:

[Binding]
public class ApiSteps : ApiStepDefinitions
{
    private readonly IDatabase _database;
    
    public ApiSteps(WebApplicationFactory<Program> factory, IDatabase database) 
        : base(factory.CreateClient()) 
    {
        _database = database;
    }
    
    [Given(@"the database contains a user with email '(.*)'")]
    public async Task GivenDatabaseContainsUser(string email)
    {
        await _database.InsertUser(new User { Email = email });
    }
    
    [Then(@"the database should contain (\d+) orders")]
    public async Task ThenDatabaseShouldContainOrders(int count)
    {
        var orders = await _database.GetOrders();
        orders.Count.Should().Be(count);
    }
}

What's Included

This package provides pre-built Reqnroll step definitions:

  • Given the following request - Send an HTTP request
  • Then the API returns the following response - Validate response with subset matching
  • Then the variable '{name}' is equals to '{value}' - Assert extracted variable value
  • Then the variable '{name}' is of type '{type}' - Assert variable type (String, Number, Boolean, Date, Object, Array, Null)
  • Then the variable '{name}' matches '{regex}' - Assert variable matches regex pattern

Dependencies

Built on top of:

License

MIT

Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.2.0-beta.4 57 10/22/2025
1.2.0-beta.3 56 10/22/2025
1.2.0-beta.2 53 10/22/2025
1.2.0-beta.1 57 10/22/2025