![]() |
VOOZH | about |
dotnet add package Rystem.Test.XUnit --version 10.0.8
NuGet\Install-Package Rystem.Test.XUnit -Version 10.0.8
<PackageReference Include="Rystem.Test.XUnit" Version="10.0.8" />
<PackageVersion Include="Rystem.Test.XUnit" Version="10.0.8" />Directory.Packages.props
<PackageReference Include="Rystem.Test.XUnit" />Project file
paket add Rystem.Test.XUnit --version 10.0.8
#r "nuget: Rystem.Test.XUnit, 10.0.8"
#:package Rystem.Test.XUnit@10.0.8
#addin nuget:?package=Rystem.Test.XUnit&version=10.0.8Install as a Cake Addin
#tool nuget:?package=Rystem.Test.XUnit&version=10.0.8Install as a Cake Tool
Rystem.Test.XUnit is a test-project helper for xUnit v3 that combines constructor injection with an optional in-process ASP.NET Core test host.
The package builds on Xunit.DependencyInjection and adds a StartupHelper model so your test project can choose between:
Microsoft.AspNetCore.TestHostIt is most useful when:
WebApplicationFactoryThe strongest source-backed examples are the package implementation itself and the real startup used by src/Core/Test/Rystem.Test.UnitTest/Startup.cs plus the HTTP tests in src/Core/Test/Rystem.Test.UnitTest/RuntimeServiceProvider/RuntimeServiceProviderTest.cs.
dotnet add package Rystem.Test.XUnit
The current 10.x package targets net10.0 and references:
Microsoft.NET.Test.SdkMicrosoft.AspNetCore.TestHostxunit.v3Xunit.DependencyInjectionRystem.DependencyInjection.WebRystem.ConcurrencyThe package is designed for xUnit v3 projects, not the older xunit package line.
The package is centered around three main pieces.
| Piece | Purpose |
|---|---|
StartupHelper |
Declarative startup model for the test project |
HostTester |
Lazily creates and starts the shared in-process test host |
TestHttpClientFactory |
Exposes IHttpClientFactory backed by the shared TestServer |
At a high level, the flow is:
Xunit.DependencyInjectionStartup : StartupHelperConfigureHost(...) builds configuration for the test runConfigureServices(...) either configures DI only or starts the shared test hostStartupHelper exposes two framework-facing methods that Xunit.DependencyInjection can call:
ConfigureHost(IHostBuilder)ConfigureServices(IServiceCollection, HostBuilderContext)From the current implementation:
ConfigureHost(...) loads AppSettingsFileName, then optional user secrets, then environment variablesConfigureServices(...) checks HasTestHostHasTestHost = true, it calls HostTester.CreateHostServerAsync(...)TestHttpClientFactory is then registered into the test DI containerConfigureClientServices(...) always runs and is where you add the services that test classes consumeSo the package gives you one central startup object for both configuration and test DI wiring.
Create a Startup class in the test project that inherits from StartupHelper.
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Rystem.Test.XUnit;
public sealed class Startup : StartupHelper
{
protected override string? AppSettingsFileName => "appsettings.test.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.AddMyBusinessLogic();
return services;
}
}
The real project already contains a concrete example in src/Core/Test/Rystem.Test.UnitTest/Startup.cs, where:
HasTestHost is trueServiceControllerHttpClient named clientAddTestServices()StartupHelper requires these members:
| Member | Purpose |
|---|---|
AppSettingsFileName |
Test configuration file name, for example appsettings.test.json |
HasTestHost |
Enables or disables the in-process ASP.NET Core host |
TypeToChooseTheRightAssemblyToRetrieveSecretsForConfiguration |
Selects the assembly used for user secrets discovery |
TypeToChooseTheRightAssemblyWithControllersToMap |
Selects the controller assembly for the test host |
ConfigureClientServices(...) |
Registers services consumed directly by test classes |
Even when the host is enabled, ConfigureClientServices(...) is still the place for the services injected into the tests themselves.
These virtual members can be overridden when needed:
| Member | Default | Purpose |
|---|---|---|
WithHttps |
true |
Configures the in-process server base address as https://localhost:443 |
PreserveExecutionContext |
false |
Passes through to TestServerOptions.PreserveExecutionContext |
AddHealthCheck |
true |
Adds /healthz and validates host startup through it |
ConfigureServerServicesAsync(...) |
no-op | Registers services inside the test server container |
ConfigureServerMiddlewareAsync(...) |
no-op | Configures the in-process middleware pipeline |
If HasTestHost is false, the server-specific members are effectively ignored.
Use this mode when you only need constructor injection and no HTTP pipeline.
public sealed class Startup : StartupHelper
{
protected override string? AppSettingsFileName => "appsettings.test.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.AddMyRepositories();
services.AddMyBusinessLogic();
return services;
}
}
public sealed class OrderServiceTest
{
private readonly IOrderService _orderService;
public OrderServiceTest(IOrderService orderService)
{
_orderService = orderService;
}
[Fact]
public async Task CreateOrder_ShouldReturnValidOrder()
{
var order = await _orderService.CreateAsync(new CreateOrderRequest { ProductId = 1 });
Assert.NotNull(order);
}
}
In this mode, the package behaves mainly as an xUnit DI bootstrapper.
Use this mode when the tests should hit a real ASP.NET Core pipeline in-process.
A trimmed version looks like this:
using Microsoft.AspNetCore.Builder;
public sealed class Startup : StartupHelper
{
protected override string? AppSettingsFileName => "appsettings.test.json";
protected override bool HasTestHost => true;
protected override Type? TypeToChooseTheRightAssemblyToRetrieveSecretsForConfiguration => typeof(Startup);
protected override Type? TypeToChooseTheRightAssemblyWithControllersToMap => typeof(Program);
protected override IServiceCollection ConfigureClientServices(
IServiceCollection services,
IConfiguration configuration)
{
services.AddHttpClient("client", x =>
{
x.BaseAddress = new Uri("https://localhost:443");
});
return services;
}
protected override ValueTask ConfigureServerServicesAsync(
IServiceCollection services,
IConfiguration configuration)
{
services.AddControllers();
services.AddMyApiServices();
return ValueTask.CompletedTask;
}
protected override ValueTask ConfigureServerMiddlewareAsync(
IApplicationBuilder app,
IServiceProvider serviceProvider)
{
app.UseRouting();
app.UseEndpoints(endpoints => endpoints.MapControllers());
return ValueTask.CompletedTask;
}
}
The real repository example is src/Core/Test/Rystem.Test.UnitTest/Startup.cs.
And a real HTTP test using constructor injection is src/Core/Test/Rystem.Test.UnitTest/RuntimeServiceProvider/RuntimeServiceProviderTest.cs:
public class RuntimeServiceProviderTest
{
private readonly HttpClient _httpClient;
public RuntimeServiceProviderTest(IHttpClientFactory httpClientFactory)
{
_httpClient = httpClientFactory.CreateClient("client");
}
[Fact]
public async Task AddOneServiceAtRuntimeAsync()
{
var response = await _httpClient.GetAsync("Service/Get");
Assert.True(response.IsSuccessStatusCode);
}
}
That pattern is the core value of the package: tests can just request IHttpClientFactory, typed clients, repositories, or any other service directly in their constructors.
When HasTestHost = true, HostTester creates the in-process server lazily and only once.
Important implementation details:
Rystem.Concurrency via ILockTestHttpClientFactory.InstanceAddApplicationPart(...)AddHealthCheck = true, the framework maps /healthz and performs a startup probe after the host startsNo real TCP port is opened. The package uses Microsoft.AspNetCore.TestHost, so requests stay in-process.
When the test host is enabled, ConfigureServices(...) registers:
services.AddSingleton<IHttpClientFactory>(TestHttpClientFactory.Instance);
TestHttpClientFactory creates clients from the shared TestServer and pre-populates these request headers:
| Header | Value |
|---|---|
Origin |
https://localhost |
Access-Control-Request-Method |
POST |
Access-Control-Request-Headers |
X-Requested-With |
That means tests can either:
IHttpClientFactory directlyConfigureClientServices(...)Example:
public sealed class HealthCheckTest
{
private readonly HttpClient _httpClient;
public HealthCheckTest(IHttpClientFactory factory)
{
_httpClient = factory.CreateClient();
}
[Fact]
public async Task HealthCheck_ShouldBeHealthy()
{
var response = await _httpClient.GetAsync("/healthz");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
}
The most useful references for this package are:
StartupHelper: src/Extensions/Tests/Rystem.Test.XUnit/TestHostWithDi/StartupHelper.csThis README is intentionally architecture-first because Rystem.Test.XUnit is mostly infrastructure. The main value is not a long API surface, but the way it composes xUnit DI, configuration loading, and an optional shared in-process ASP.NET Core host.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 net10.0 is compatible. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
This package is not used by any NuGet packages.
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 10.0.8 | 5,913 | 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 | 162 | 2/9/2026 |
| 10.0.3 | 147,971 | 1/28/2026 |
| 10.0.1 | 209,123 | 11/12/2025 |
| 9.1.3 | 293 | 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 | 238 | 3/16/2025 |
| 9.0.26 | 256 | 3/13/2025 |
| 9.0.25 | 52,140 | 3/9/2025 |
| 9.0.21 | 358 | 3/6/2025 |
| 9.0.20 | 19,579 | 3/6/2025 |