Rystem.Test.XUnit
10.0.1
See the version list below for details.
dotnet add package Rystem.Test.XUnit --version 10.0.1
NuGet\Install-Package Rystem.Test.XUnit -Version 10.0.1
<PackageReference Include="Rystem.Test.XUnit" Version="10.0.1" />
<PackageVersion Include="Rystem.Test.XUnit" Version="10.0.1" />
<PackageReference Include="Rystem.Test.XUnit" />
paket add Rystem.Test.XUnit --version 10.0.1
#r "nuget: Rystem.Test.XUnit, 10.0.1"
#:package Rystem.Test.XUnit@10.0.1
#addin nuget:?package=Rystem.Test.XUnit&version=10.0.1
#tool nuget:?package=Rystem.Test.XUnit&version=10.0.1
What is Rystem?
Rystem.Test.XUnit
Advanced XUnit integration testing framework with built-in Dependency Injection and ASP.NET Core Test Host support. Perfect for testing APIs, services, and complex application architectures.
π¦ Installation
dotnet add package Rystem.Test.XUnit
β οΈ Important: XUnit v3 Requirements
Rystem.Test.XUnit requires XUnit v3. After recent XUnit updates, ensure you have the correct package versions installed.
Required packages in your test project:
<ItemGroup>
<PackageReference Include="Rystem.Test.XUnit" Version="9.1.3" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="xunit.v3" Version="3.0.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.4">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
Key differences from XUnit v2:
- β
Use
xunit.v3instead ofxunitpackage - β
Use
xunit.runner.visualstudioversion 3.1.4+ - β Constructor injection still works the same way
- β οΈ Some breaking changes in assertion APIs (see XUnit v3 migration guide)
Quick project setup:
# Create test project
dotnet new xunit -n MyApp.Tests
# Remove old xunit package (if present)
dotnet remove package xunit
# Add required packages
dotnet add package Rystem.Test.XUnit
dotnet add package xunit.v3 --version 3.0.1
dotnet add package xunit.runner.visualstudio --version 3.1.4
dotnet add package Microsoft.NET.Test.Sdk --version 17.14.1
π― Features
- β Built-in Dependency Injection for XUnit tests
- β ASP.NET Core Test Server with full middleware pipeline
- β Automatic Controller Discovery and mapping
- β User Secrets Integration for configuration management
- β Health Check Support for server validation
- β HTTPS/HTTP Configuration with customizable options
- β Automatic HttpClient Factory for API testing
π Quick Start
Scenario 1: Testing Without HTTP Server (Business Logic Only)
Perfect for testing services, repositories, and business logic without API layer.
1. Create Startup Class
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Rystem.Test.XUnit;
namespace MyApp.Tests
{
public class Startup : StartupHelper
{
protected override string? AppSettingsFileName => "appsettings.json";
protected override bool HasTestHost => false; // β No HTTP server
protected override Type? TypeToChooseTheRightAssemblyToRetrieveSecretsForConfiguration => typeof(Startup);
protected override Type? TypeToChooseTheRightAssemblyWithControllersToMap => null; // Not needed
protected override IServiceCollection ConfigureClientServices(IServiceCollection services, IConfiguration configuration)
{
// Add your business services here
services.AddMyBusinessLogic();
services.AddMyRepositories();
services.AddMyTestHelpers();
return services;
}
}
}
2. Write Tests with Constructor Injection
using Xunit;
namespace MyApp.Tests
{
public class BusinessLogicTest
{
private readonly IBookManager _bookManager;
private readonly ITestHelpers _testHelpers;
// Constructor injection works automatically! π
public BusinessLogicTest(IBookManager bookManager, ITestHelpers testHelpers)
{
_bookManager = bookManager;
_testHelpers = testHelpers;
}
[Fact]
public async Task CreateBook_ShouldReturnValidBook()
{
// Arrange
var bookId = Guid.NewGuid();
// Act
var book = await _bookManager.CreateBookAsync(bookId);
// Assert
Assert.NotNull(book);
Assert.Equal(bookId, book.Id);
}
[Theory]
[InlineData("Title 1")]
[InlineData("Title 2")]
public async Task UpdateBookTitle_ShouldSucceed(string title)
{
// Arrange
var book = await _testHelpers.CreateTestBookAsync();
// Act
await _bookManager.UpdateTitleAsync(book.Id, title);
var updated = await _bookManager.GetByIdAsync(book.Id);
// Assert
Assert.Equal(title, updated.Title);
}
}
}
Scenario 2: Testing With HTTP Server (Full API Integration)
Full integration testing with real HTTP requests to your ASP.NET Core API.
1. Create Startup Class with Test Host
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Rystem.Test.XUnit;
namespace MyApp.Api.Tests
{
public class Startup : StartupHelper
{
protected override string? AppSettingsFileName => "appsettings.test.json";
protected override bool HasTestHost => true; // β
Enable HTTP server
protected override bool WithHttps => true; // HTTPS enabled
protected override bool AddHealthCheck => true; // Add /healthz endpoint
// Use Startup or any controller class from your API project
protected override Type? TypeToChooseTheRightAssemblyWithControllersToMap => typeof(Program); // Your API's Program class
// Use test project's Startup to load User Secrets
protected override Type? TypeToChooseTheRightAssemblyToRetrieveSecretsForConfiguration => typeof(Startup);
// Configure services for TEST CLIENT (HTTP consumers)
protected override IServiceCollection ConfigureClientServices(IServiceCollection services, IConfiguration configuration)
{
// Add HTTP client to call your API
services.AddHttpClient<IMyApiClient, MyApiClient>(client =>
{
client.BaseAddress = new Uri("https://localhost");
});
return services;
}
// Configure services for TEST SERVER (API dependencies)
protected override async ValueTask ConfigureServerServicesAsync(IServiceCollection services, IConfiguration configuration)
{
// Add your API services
services.AddControllers();
services.AddEndpointsApiExplorer();
services.AddMyApiServices();
services.AddMyRepositories();
services.AddMyBusinessLogic();
return;
}
// Configure middleware pipeline for TEST SERVER
protected override async ValueTask ConfigureServerMiddlewareAsync(IApplicationBuilder app, IServiceProvider serviceProvider)
{
// Configure your API middleware chain
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
return;
}
}
}
2. Write API Tests with HTTP Calls
using Xunit;
namespace MyApp.Api.Tests
{
public class ApiIntegrationTest
{
private readonly IMyApiClient _apiClient;
public ApiIntegrationTest(IMyApiClient apiClient)
{
_apiClient = apiClient;
}
[Theory]
[InlineData("user@example.com")]
[InlineData("admin@example.com")]
public async Task Login_ShouldReturnUser(string email)
{
// Act - Real HTTP call to localhost test server
var user = await _apiClient.LoginAsync(email);
// Assert
Assert.NotNull(user);
Assert.Equal(email, user.Email);
}
[Fact]
public async Task GetBooks_ShouldReturnList()
{
// Arrange
await _apiClient.LoginAsync("user@example.com");
// Act
var books = await _apiClient.GetBooksAsync();
// Assert
Assert.NotEmpty(books);
}
[Fact]
public async Task CreateBook_ThenGetById_ShouldMatch()
{
// Arrange
var createRequest = new CreateBookRequest
{
Title = "Test Book",
Author = "Test Author"
};
// Act
var created = await _apiClient.CreateBookAsync(createRequest);
var retrieved = await _apiClient.GetBookByIdAsync(created.Id);
// Assert
Assert.Equal(created.Id, retrieved.Id);
Assert.Equal(createRequest.Title, retrieved.Title);
}
}
}
π Configuration Reference
StartupHelper Properties
AppSettingsFileName (Required)
Path to your appsettings file for test configuration.
protected override string? AppSettingsFileName => "appsettings.test.json";
Best Practice: Use separate appsettings.test.json for test configurations.
HasTestHost (Required)
Enables or disables the ASP.NET Core Test Server.
protected override bool HasTestHost => true; // Enable test server
protected override bool HasTestHost => false; // Disable (business logic only)
When to enable:
- β Testing REST APIs, gRPC services, or SignalR hubs
- β Testing middleware pipeline behavior
- β Testing authentication/authorization flows
- β Integration tests requiring real HTTP requests
When to disable:
- β Testing business logic without HTTP layer
- β Repository pattern tests
- β Service layer unit tests
TypeToChooseTheRightAssemblyWithControllersToMap (Required if HasTestHost = true)
Specifies which assembly contains your API controllers/endpoints.
// Option 1: Use Program class from your API project
protected override Type? TypeToChooseTheRightAssemblyWithControllersToMap => typeof(Program);
// Option 2: Use any controller from your API project
protected override Type? TypeToChooseTheRightAssemblyWithControllersToMap => typeof(BooksController);
// Option 3: Not needed if HasTestHost = false
protected override Type? TypeToChooseTheRightAssemblyWithControllersToMap => null;
Purpose: Automatically discovers and maps all controllers/endpoints in that assembly.
TypeToChooseTheRightAssemblyToRetrieveSecretsForConfiguration (Required)
Specifies which assembly to load User Secrets from (Visual Studio Secret Manager).
// Usually points to your TEST project's Startup class
protected override Type? TypeToChooseTheRightAssemblyToRetrieveSecretsForConfiguration => typeof(Startup);
User Secrets Example:
{
"ConnectionStrings:Database": "Server=...;Database=Test;",
"ExternalApi:ApiKey": "secret-key-here"
}
How to manage secrets:
# Right-click test project in Visual Studio β Manage User Secrets
# Or use CLI:
dotnet user-secrets set "ConnectionStrings:Database" "Server=localhost;..."
WithHttps (Optional, default: true)
Configures test server to use HTTPS.
protected override bool WithHttps => true; // https://localhost:443
protected override bool WithHttps => false; // http://localhost
PreserveExecutionContext (Optional, default: false)
Preserves execution context across async operations.
protected override bool PreserveExecutionContext => true;
When to enable:
- Testing code that relies on
AsyncLocal<T> - Testing authentication contexts that flow across async calls
AddHealthCheck (Optional, default: true)
Automatically adds /healthz endpoint to test server.
protected override bool AddHealthCheck => true; // Adds /healthz endpoint
Validation: Test framework automatically calls /healthz after server startup to ensure health.
Abstract Methods
ConfigureClientServices (Required)
Configure dependency injection for test consumers (your test classes).
protected override IServiceCollection ConfigureClientServices(IServiceCollection services, IConfiguration configuration)
{
// Add services used by test classes
services.AddHttpClient<IMyApiClient, MyApiClient>(client =>
{
client.BaseAddress = new Uri("https://localhost");
});
services.AddSingleton<ITestDataGenerator, TestDataGenerator>();
return services;
}
ConfigureServerServicesAsync (Virtual, implement if HasTestHost = true)
Configure dependency injection for test server (API dependencies).
protected override async ValueTask ConfigureServerServicesAsync(IServiceCollection services, IConfiguration configuration)
{
// Add your API services here
services.AddControllers();
services.AddMyBusinessLogic();
services.AddMyRepositories();
// Configure database for testing
services.AddDbContext<MyDbContext>(options =>
options.UseInMemoryDatabase("TestDb"));
return;
}
ConfigureServerMiddlewareAsync (Virtual, implement if HasTestHost = true)
Configure middleware pipeline for test server.
protected override async ValueTask ConfigureServerMiddlewareAsync(IApplicationBuilder app, IServiceProvider serviceProvider)
{
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseCors("AllowAll");
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapGrpcService<MyGrpcService>();
});
return;
}
ποΈ Architecture
How It Works
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β XUnit Test Runner β
βββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β StartupHelper (Abstract Base) β
β - Loads appsettings.json β
β - Loads User Secrets β
β - Configures Host Builder β
βββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββ
β
βββββββββββββββ΄ββββββββββββββ
βΌ βΌ
βββββββββββββββββββββββββββ βββββββββββββββββββββββββββββββ
β ConfigureClientServicesβ β ConfigureServerServicesAsyncβ
β (Test DI Container) β β (API DI Container) β
β - HTTP Clients β β - Controllers β
β - Test Helpers β β - Business Logic β
β - Mocks β β - Repositories β
βββββββββββββ¬ββββββββββββββ ββββββββββββ¬βββββββββββββββββββ
β β
β βββββββββββββ΄ββββββββββββββββ
β βΌ β
β ββββββββββββββββββββββββββββββ β
β β ASP.NET Core Test Server β β
β β - Middleware Pipeline β β
β β - Endpoint Routing β β
β β - Authentication β β
β ββββββββββββββ¬ββββββββββββββββ β
β β β
βΌ βΌ βΌ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Constructor Injection in Test Classes β
β public MyTest(IMyService service, IHttpClientFactory http) β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
π§ͺ Real-World Examples
Example 1: Repository Pattern Testing
public class Startup : StartupHelper
{
protected override string? AppSettingsFileName => "appsettings.json";
protected override bool HasTestHost => false;
protected override Type? TypeToChooseTheRightAssemblyToRetrieveSecretsForConfiguration => typeof(Startup);
protected override Type? TypeToChooseTheRightAssemblyWithControllersToMap => null;
protected override IServiceCollection ConfigureClientServices(IServiceCollection services, IConfiguration configuration)
{
services.AddDbContext<TestDbContext>(options =>
options.UseInMemoryDatabase("TestDb"));
services.AddRepositories();
return services;
}
}
public class RepositoryTest
{
private readonly IRepository<Book> _bookRepository;
public RepositoryTest(IRepository<Book> bookRepository)
{
_bookRepository = bookRepository;
}
[Fact]
public async Task InsertAndRetrieve_ShouldWork()
{
var book = new Book { Id = Guid.NewGuid(), Title = "Test" };
await _bookRepository.InsertAsync(book);
var retrieved = await _bookRepository.GetAsync(book.Id);
Assert.Equal(book.Title, retrieved.Title);
}
}
Example 2: Full API Testing with Authentication
public class Startup : StartupHelper
{
protected override string? AppSettingsFileName => "appsettings.test.json";
protected override bool HasTestHost => true;
protected override Type? TypeToChooseTheRightAssemblyWithControllersToMap => typeof(Program);
protected override Type? TypeToChooseTheRightAssemblyToRetrieveSecretsForConfiguration => typeof(Startup);
protected override IServiceCollection ConfigureClientServices(IServiceCollection services, IConfiguration configuration)
{
services.AddHttpClient<IBridgeInspectionApi, BridgeInspectionApi>(client =>
{
client.BaseAddress = new Uri("https://localhost");
});
return services;
}
protected override async ValueTask ConfigureServerServicesAsync(IServiceCollection services, IConfiguration configuration)
{
services.AddControllers();
services.AddAuthentication("TestScheme")
.AddScheme<TestAuthOptions, TestAuthHandler>("TestScheme", options => { });
services.AddAuthorization();
services.AddMyApiServices();
return;
}
protected override async ValueTask ConfigureServerMiddlewareAsync(IApplicationBuilder app, IServiceProvider serviceProvider)
{
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints => endpoints.MapControllers());
return;
}
}
public class InspectionTest
{
private readonly IBridgeInspectionApi _api;
public InspectionTest(IBridgeInspectionApi api)
{
_api = api;
}
[Theory]
[InlineData("user@example.com")]
public async Task GetBridgesAndInspections_ShouldReturnData(string email)
{
// Login
var user = await _api.LoginAsAsync(email);
Assert.NotNull(user);
// Get bridges
var bridges = await _api.BridgeApi.ListAsync();
Assert.NotEmpty(bridges);
// Get inspections for first bridge
var bridge = bridges.First();
var inspections = await _api.InspectionApi.ListAsync(bridge.Id);
Assert.NotEmpty(inspections);
}
[Fact]
public async Task DownloadInspectionZip_ShouldReturnBytes()
{
await _api.LoginAsAsync("admin@example.com");
var bridges = await _api.BridgeApi.ListAsync();
var inspections = await _api.InspectionApi.ListAsync(bridges.First().Id);
var zipBytes = await _api.InspectionApi.DownloadInspectionsFile(
bridges.First().Id,
inspections.First().Id
);
Assert.NotEmpty(zipBytes);
}
}
π§ Advanced Configuration
Custom HTTP Headers for Test Server
services.AddHttpClient<IMyClient, MyClient>((serviceProvider, client) =>
{
client.BaseAddress = new Uri("https://localhost");
client.DefaultRequestHeaders.Add("X-Test-Environment", "true");
client.DefaultRequestHeaders.Add("X-Api-Key", "test-key");
});
Mock External Dependencies
protected override IServiceCollection ConfigureClientServices(IServiceCollection services, IConfiguration configuration)
{
// Replace real external API with mock
services.AddSingleton<IExternalApiClient, MockExternalApiClient>();
return services;
}
In-Memory Database for Tests
protected override async ValueTask ConfigureServerServicesAsync(IServiceCollection services, IConfiguration configuration)
{
services.AddDbContext<MyDbContext>(options =>
options.UseInMemoryDatabase($"TestDb_{Guid.NewGuid()}")); // Unique per test run
return;
}
π Resources
- π Complete Documentation: https://rystem.net
- π€ MCP Server for AI: https://rystem.cloud/mcp
- π¬ Discord Community: https://discord.gg/tkWvy4WPjt
- β Support the Project: https://www.buymeacoffee.com/keyserdsoze
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. 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. |
-
net10.0
- Microsoft.AspNetCore.TestHost (>= 10.0.0)
- Microsoft.NET.Test.Sdk (>= 18.0.1)
- Rystem.Concurrency (>= 10.0.1)
- Rystem.DependencyInjection.Web (>= 10.0.1)
- Xunit.DependencyInjection (>= 11.1.0)
- xunit.v3 (>= 3.2.0)
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 |
|---|---|---|
| 10.0.8 | 5,916 | 5/13/2026 |
| 10.0.7 | 196 | 3/26/2026 |
| 10.0.6 | 433,590 | 3/3/2026 |
| 10.0.5 | 154 | 2/22/2026 |
| 10.0.4 | 163 | 2/9/2026 |
| 10.0.3 | 147,971 | 1/28/2026 |
| 10.0.1 | 209,124 | 11/12/2025 |
| 9.1.3 | 294 | 9/2/2025 |
| 9.1.2 | 764,557 | 5/29/2025 |
| 9.1.1 | 97,839 | 5/2/2025 |
| 9.0.32 | 186,802 | 4/15/2025 |
| 9.0.31 | 5,851 | 4/2/2025 |
| 9.0.30 | 88,873 | 3/26/2025 |
| 9.0.29 | 9,009 | 3/18/2025 |
| 9.0.28 | 244 | 3/17/2025 |
| 9.0.27 | 239 | 3/16/2025 |
| 9.0.26 | 256 | 3/13/2025 |
| 9.0.25 | 52,141 | 3/9/2025 |
| 9.0.21 | 358 | 3/6/2025 |
| 9.0.20 | 19,579 | 3/6/2025 |