SaaSCore.Testing
0.5.0-preview.4
dotnet add package SaaSCore.Testing --version 0.5.0-preview.4
NuGet\Install-Package SaaSCore.Testing -Version 0.5.0-preview.4
<PackageReference Include="SaaSCore.Testing" Version="0.5.0-preview.4" />
<PackageVersion Include="SaaSCore.Testing" Version="0.5.0-preview.4" />
<PackageReference Include="SaaSCore.Testing" />
paket add SaaSCore.Testing --version 0.5.0-preview.4
#r "nuget: SaaSCore.Testing, 0.5.0-preview.4"
#:package SaaSCore.Testing@0.5.0-preview.4
#addin nuget:?package=SaaSCore.Testing&version=0.5.0-preview.4&prerelease
#tool nuget:?package=SaaSCore.Testing&version=0.5.0-preview.4&prerelease
SaaSCore.Testing
SaaSCore.Testing provides a lightweight set of helpers, fixtures, and
utilities to support writing fast, reliable, and repeatable tests across
the SaaSCore ecosystem.
It is designed to remove boilerplate when testing database interactions,
specifications, and application-level components --- while remaining
completely optional and unobtrusive.
This package is used internally by SaaSCore and SaaSCore.* modules
but can also be referenced by consuming applications.
✨ Features
API regression harness
Starts an isolated ASP.NET Core API withWebApplicationFactory<TEntryPoint>, per-scenario configuration, service replacement, test authentication, scoped seeding/resetting, outbound HTTP stubs, and captured logs for diagnostics.SaaSCore EF Core test contexts
Provides preconfigured in-memory and SQLite-backedSaaSCoreDbContextfactories for validating repository queries, specifications, interceptors, and tenant-aware persistence logic.Automatic test database bootstrapping
Creates and seeds a fresh database instance per test (or per test class), ensuring test isolation.Extension points for custom seeding
Easily inject your own seed data for database tests and API regression scenarios.Failure simulation
Includes EF Core interceptors for deliberately failing queries or saves so error paths can be tested without brittle mocks.
📦 Installation
This package is internal for now. Once extracted into its own NuGet package, it will be installed via:
dotnet add package SaaSCore.Testing
Until then, reference the project from your solution:
<ItemGroup>
<ProjectReference Include="..\SaaSCore.Testing\SaaSCore.Testing.csproj" />
</ItemGroup>
🚀 Getting Started
1. Create an isolated API regression harness
Add a public partial Program type to the API project under test if it
uses top-level statements:
public partial class Program;
Then create a harness per test or per Reqnroll scenario:
await using var harness = await RegressionHarness
.ForApi<Program>()
.WithEnvironment("Regression")
.WithConfiguration("FeatureFlags:UseFakePayments", "true")
.WithServices(services =>
{
services.ReplaceScoped<IMyDependency, FakeMyDependency>();
})
.WithTestAuthentication(user => user
.WithSubject("test-user")
.WithClaim("tenant_id", "tenant-001")
.WithRole("admin"))
.BuildAsync();
var response = await harness.Api.PostAsJsonAsync("/orders", request);
The harness exposes:
harness.Apifor API-focused HTTP calls and diagnostic success assertions.harness.Services,CreateScope(), andCreateAsyncScope()for assertions against application services.WithSeeder(...),WithScopedSeeder(...),WithReset(...), andResetAsync()for scenario-level setup and cleanup.harness.Logsfor captured application logs.
2. Stub outbound HTTP calls
For services registered with IHttpClientFactory, replace named clients
with deterministic local handlers:
await using var harness = await RegressionHarness
.ForApi<Program>()
.WithHttpClientStub("payments", stub =>
{
stub.When(HttpMethod.Post, "/charges")
.RespondJson(new { id = "ch_test_123", status = "approved" });
})
.BuildAsync();
var payments = harness.GetHttpClientStub("payments");
Assert.Single(payments.Interactions);
If a response is expected to be successful, use the diagnostic helper:
await harness.Api.GetAndEnsureSuccessAsync("/health");
On failure, RegressionApiRequestException includes the request, status
code, response body, and recent captured warning/error logs.
3. Use with Reqnroll
SaaSCore.Testing does not force a Reqnroll dependency on every
consumer. The package provides a framework-agnostic
RegressionScenarioContext<TEntryPoint> that can be registered in
Reqnroll's scenario container:
[Binding]
public sealed class RegressionHooks
{
private readonly RegressionScenarioContext<Program> scenario;
public RegressionHooks(RegressionScenarioContext<Program> scenario)
{
this.scenario = scenario;
}
[BeforeScenario]
public Task BeforeScenario()
=> scenario
.Configure(builder => builder
.WithEnvironment("Regression")
.WithConfiguration("FeatureFlags:UseFakePayments", "true"))
.StartAsync();
[AfterScenario]
public async Task AfterScenario()
{
await scenario.DisposeAsync();
}
}
[Binding]
public sealed class OrderSteps
{
private readonly RegressionScenarioContext<Program> scenario;
public OrderSteps(RegressionScenarioContext<Program> scenario)
{
this.scenario = scenario;
}
[When("I create an order")]
public async Task WhenICreateAnOrder()
{
await scenario.Api.PostAsJsonAsync("/orders", new CreateOrderRequest());
}
}
Register RegressionScenarioContext<Program> as a scenario-scoped
dependency in the Reqnroll project. Each scenario gets its own harness,
client, service provider, logs, stubs, and reset lifecycle.
4. Use the SaaSCore database factories
var tenantContext = TenantContextFactory.Create();
await using var context = SaaSCoreDbContextTestFactory.CreateInMemory(tenantContext);
For relational behavior such as constraints, transactions, or command interceptors, use the SQLite-backed factory:
await using var context = SaaSCoreDbContextTestFactory.CreateSqliteInMemory(tenantContext);
5. Seed your test data
var data = new TestDataBuilder<SaaSCoreDbContext>(context);
data.Add(entity);
await data.SaveAsync();
🧰 Included Utilities
Utility Description
RegressionHarness Fluent entry point for isolated API
regression tests.
RegressionScenarioContext Scenario-scoped harness wrapper for
Reqnroll and other BDD runners.
HttpClientStub Lightweight outbound HTTP simulation for
named HttpClient dependencies.
SaaSCoreDbContextTestFactory Creates in-memory and SQLite-backed
SaaSCore DbContext instances.
TenantContextFactory Creates tenant contexts for tenant-aware
tests.
TestDataBuilder Small helper for adding and saving test
entities.
ThrowOnCommandInterceptor Simulates query/command failures.
ThrowOnSaveChangesInterceptor Simulates persistence failures.
📐 Design Principles
SaaSCore.Testing follows the core SaaSCore philosophy:
- No mocking what you can prove --- prefer real EF Core behaviour over mocked abstractions.
- Realistic data, minimal ceremony --- seed helpers give structure without forcing test complexity.
- Composable, not prescriptive --- all utilities can be replaced or extended.
🗺 Roadmap
- Snapshot testing helpers.
- Optional package-level Reqnroll bindings if consumers want a direct Reqnroll integration package later.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. 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. |
-
net8.0
- Microsoft.AspNetCore.Mvc.Testing (>= 8.0.22)
- Microsoft.EntityFrameworkCore.InMemory (>= 8.0.22)
- Microsoft.EntityFrameworkCore.Sqlite (>= 8.0.22)
- NSubstitute (>= 5.3.0)
- SaaSCore.Persistence (>= 0.5.0-preview.4)
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 |
|---|---|---|
| 0.5.0-preview.4 | 0 | 5/14/2026 |
| 0.5.0-preview.3 | 0 | 5/14/2026 |
| 0.5.0-preview.2 | 64 | 3/29/2026 |
| 0.5.0-preview.1 | 60 | 3/8/2026 |
| 0.4.0-preview.1 | 61 | 2/9/2026 |
| 0.3.0-preview.3 | 64 | 1/11/2026 |
| 0.3.0-preview.2 | 68 | 1/5/2026 |
| 0.3.0-preview.1 | 71 | 1/5/2026 |
| 0.2.0-preview.15 | 121 | 12/14/2025 |
| 0.2.0-preview.14 | 115 | 12/14/2025 |
| 0.2.0-preview.13 | 112 | 12/14/2025 |
| 0.2.0-preview.12 | 86 | 12/12/2025 |
| 0.2.0-preview.11 | 187 | 12/11/2025 |
| 0.2.0-preview.10 | 384 | 12/11/2025 |
| 0.2.0-preview.9 | 384 | 12/11/2025 |
| 0.2.0-preview.8 | 383 | 12/11/2025 |
| 0.2.0-preview.7 | 381 | 12/11/2025 |
| 0.2.0-preview.6 | 372 | 12/11/2025 |
| 0.2.0-preview.5 | 378 | 12/11/2025 |
| 0.2.0-preview.4 | 383 | 12/11/2025 |