PQSoft.ReqNRoll
1.2.0-beta.4
dotnet add package PQSoft.ReqNRoll --version 1.2.0-beta.4
NuGet\Install-Package PQSoft.ReqNRoll -Version 1.2.0-beta.4
<PackageReference Include="PQSoft.ReqNRoll" Version="1.2.0-beta.4" />
<PackageVersion Include="PQSoft.ReqNRoll" Version="1.2.0-beta.4" />
<PackageReference Include="PQSoft.ReqNRoll" />
paket add PQSoft.ReqNRoll --version 1.2.0-beta.4
#r "nuget: PQSoft.ReqNRoll, 1.2.0-beta.4"
#:package PQSoft.ReqNRoll@1.2.0-beta.4
#addin nuget:?package=PQSoft.ReqNRoll&version=1.2.0-beta.4&prerelease
#tool nuget:?package=PQSoft.ReqNRoll&version=1.2.0-beta.4&prerelease
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 requestThen the API returns the following response- Validate response with subset matchingThen the variable '{name}' is equals to '{value}'- Assert extracted variable valueThen 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:
- PQSoft.HttpFile - HTTP request/response parsing
- PQSoft.JsonComparer - Smart JSON comparison with token extraction
- Reqnroll - BDD test framework
License
MIT
| Product | Versions 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. |
-
net9.0
- AwesomeAssertions (>= 9.1.0)
- PQSoft.HttpFile (>= 1.2.0-beta.4)
- PQSoft.JsonComparer (>= 1.2.0-beta.4)
- PQSoft.JsonComparer.AwesomeAssertions (>= 1.2.0-beta.4)
- Reqnroll (>= 2.4.0)
- System.Linq.Async (>= 6.0.1)
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 |