MintPlayer.Spark.Testing
10.0.0-preview.52
See the version list below for details.
dotnet add package MintPlayer.Spark.Testing --version 10.0.0-preview.52
NuGet\Install-Package MintPlayer.Spark.Testing -Version 10.0.0-preview.52
<PackageReference Include="MintPlayer.Spark.Testing" Version="10.0.0-preview.52" />
<PackageVersion Include="MintPlayer.Spark.Testing" Version="10.0.0-preview.52" />
<PackageReference Include="MintPlayer.Spark.Testing" />
paket add MintPlayer.Spark.Testing --version 10.0.0-preview.52
#r "nuget: MintPlayer.Spark.Testing, 10.0.0-preview.52"
#:package MintPlayer.Spark.Testing@10.0.0-preview.52
#addin nuget:?package=MintPlayer.Spark.Testing&version=10.0.0-preview.52&prerelease
#tool nuget:?package=MintPlayer.Spark.Testing&version=10.0.0-preview.52&prerelease
MintPlayer.Spark.Testing
Test-utilities library for writing automated tests against Spark apps. It provides an embedded RavenDB driver, an in-memory Spark host factory, antiforgery-aware HTTP helpers, JSON fixture seeding, index helpers, and Verify snapshot defaults.
This is a test-utilities library, not a test project — it references xUnit for the
IAsyncLifetimetype but contains no[Fact]s (IsTestProject=false). Add it to your own xUnit test project as a<PackageReference>. It is xUnit-based and pulls inRavenDB.TestDriver(which bundles an embedded RavenDB server) andVerify.Xunit, so treat it as a batteries-included integration-test harness.
What's in the box
| Type | Purpose |
|---|---|
SparkTestDriver |
xUnit base class that creates a fresh in-memory RavenDB database per test case and exposes a ready IDocumentStore Store. |
SparkEndpointFactory<TContext> |
Boots a minimal in-memory Spark HTTP host (ASP.NET Core TestServer) wired to a supplied store, for endpoint/integration tests. |
SparkTestClient |
HttpClient wrapper that attaches the antiforgery cookie + X-XSRF-TOKEN header to every mutating request. |
JsonFixtureImporter |
Seeds a store from RavenDB query-result-format JSON fixture files. |
RavenIndexHelper |
Deploys indexes and waits for them to be registered and non-stale (usable from any store-holding fixture). |
AsyncWait |
Bounded polling for work with no completion signal; always throws on expiry. |
RavenIndexDeploymentException |
An index faulted or was never deployed — distinct from a timeout. |
VerifyDefaults |
Centralizes Verify snapshot path configuration (auto-initialized via a module initializer). |
Setup
1. Reference the project
<ItemGroup>
<ProjectReference Include="..\MintPlayer.Spark.Testing\MintPlayer.Spark.Testing.csproj" />
</ItemGroup>
2. Provide a RavenDB license
RavenDB 7.x requires a license even for the embedded TestDriver. SparkTestDriver loads one from, in order:
- The
RAVENDB_LICENSEenvironment variable (JSON content — CI-friendly). - A
raven-license.logfile at the repository root (local development).
If neither is present, tests fail at initialization with a clear message. See ravendb.net/buy for community/developer licenses.
Usage
Data-layer tests — SparkTestDriver
Derive from SparkTestDriver to get an embedded store. Override IndexAssemblies to auto-deploy and wait on indexes before the test body runs.
Each test case gets its own database. xUnit constructs a new instance of the test class for every
[Fact]and every[Theory]row, soInitializeAsync— and theCreateDatabaseOperationbehind it — runs per test, not per class. All of those databases live on one shared embedded server, so a large suite should cap test parallelism inxunit.runner.json; running unconstrained can make the server unresponsive under CI load.
public class PersonQueryTests : SparkTestDriver
{
// Indexes in this assembly are deployed and awaited during InitializeAsync.
protected override IEnumerable<Assembly> IndexAssemblies => [typeof(People_ByName).Assembly];
[Fact]
public async Task Finds_people_by_name()
{
await SeedFromJsonAsync("Data/Seed/people.json"); // resolves against the test output dir
using var session = Store.OpenAsyncSession();
var matches = await session.Query<Person, People_ByName>()
.Where(p => p.Name == "Ada")
.ToListAsync();
matches.Should().ContainSingle();
}
}
Copy fixtures to the output directory so the relative path resolves:
<ItemGroup>
<Content Include="Data\**\*"><CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory></Content>
</ItemGroup>
Seeding that is queryable — SeedAsync
This is the default way to write documents a test will then query. It saves with
WaitForIndexesAfterSaveChanges(throwOnTimeout: true), so the server holds the write until the
indexes covering it are current — no explicit index wait needed afterwards:
var car = new Car { Plate = "ABC-123" };
await SeedAsync(session => session.StoreAsync(car));
// query immediately; no WaitForIndexing
using var session = Store.OpenAsyncSession();
var hits = await session.Query<Car, Cars_ByPlate>().Where(c => c.Plate == "ABC-123").ToListAsync();
Why this beats saving and then polling every index:
- Targeted — only the indexes this write touched, not the whole database.
- No sampling window — a global poll can catch a momentarily-clean snapshot and return while another writer's document is still unindexed. The write here does not complete until its indexes are current, so there is no gap to lose.
- Impossible to forget — the guarantee is attached to the write, not to a call someone has to remember to add next to each new query.
Declare the entity outside the lambda if you need its generated id afterwards. Reach for
WaitForIndexesAsync instead when no single session owns the write — Smuggler/JSON imports, or
documents written by a background worker or by the code under test.
Waiting for anything else — AsyncWait
For asynchronous work with no completion signal (a worker attaching, a file-watcher invalidating a cache, a cron job firing). Everything here throws on expiry, naming what was awaited and for how long — a wait that quietly gives up turns into a confusing assertion failure somewhere downstream.
await AsyncWait.UntilAsync(
() => recorder.Count(nameof(EverySecondJob)) > 0,
"the every-second job to fire at least once",
TimeSpan.FromSeconds(8));
var message = await AsyncWait.ForAsync(
() => session.LoadAsync<SparkMessage>(id),
m => m.Status == EMessageStatus.Processed,
$"message '{id}' to be processed",
describeLast: m => $"Status={m?.Status}");
Prefer a real signal where one exists: SeedAsync for writes, WaitForIndexesAsync for indexing.
Never substitute a fixed Task.Delay — it makes a test that passes prove only "not yet".
Waiting for indexes — WaitForIndexesAsync
"Settled" means deployed and up to date, not just up to date:
await WaitForIndexesAsync(); // on SparkTestDriver — carries this fixture's declared index names
The second half alone is a trap. "Every index is non-stale" is universally quantified, so on a
database with no indexes — where every fixture starts, since each test gets its own — it is
vacuously true and returns instantly, having guaranteed nothing. SparkTestDriver remembers the
indexes it deployed (from IndexAssemblies or DeployIndexesAsync) and passes them along, so a
wait cannot pass because the index it was waiting for was never registered.
On a plain store, name them yourself:
await store.WaitForIndexingAsync(expectedIndexes: RavenIndexHelper.DeclaredIndexNames(myAssembly));
Auto-indexes are the exception, and it is inherent. They are held to the same staleness bar as declared indexes — a stale auto-index is exactly what returns the wrong rows — but they cannot take part in the deployment check, because they do not exist until a query creates them. RavenDB blocks on that first creation itself, which is what makes plain seed-then-query safe.
Failures are typed, because the causes are unrelated:
| Exception | Meaning | What to do |
|---|---|---|
RavenIndexDeploymentException |
An index faulted, or was never registered. Carries FaultedIndexes / MissingIndexes and the index errors. |
Fix the index — waiting will never help. |
TimeoutException |
Indexes are healthy but did not catch up in time. | Retry, raise the limit, or look at load. |
Endpoint/integration tests — SparkEndpointFactory<TContext>
Boots a real Spark middleware pipeline over TestServer, against a store you supply (typically Store from a SparkTestDriver). It writes the supplied model definitions into a per-test temp content root, so ModelLoader sees exactly the entity types your fixture declares.
public class CarEndpointTests : SparkTestDriver
{
[Fact]
public async Task Create_then_get_round_trips()
{
await using var factory = new SparkEndpointFactory<FleetContext>(
testStore: Store,
models: FleetModels.All,
configureServices: services =>
{
// Optional: register custom Actions, swap IAccessControl for authz tests, etc.
});
// Antiforgery-aware client: warms up to mint the XSRF token, then attaches it to writes.
using var client = await factory.CreateAuthorizedClientAsync();
var create = await client.PostJsonAsync("/spark/po/Car", new { Brand = "Tesla" });
create.EnsureSuccessStatusCode();
var list = await client.GetAsync("/spark/po/Car");
list.EnsureSuccessStatusCode();
}
}
By default the factory opts into
AllowAnonymousAccess()so endpoint logic can be tested under an "everyone-can" baseline (the framework default is deny-all). Tests that exercise authorization should register their ownIAccessControlviaconfigureServices.
TestServer's HttpClient does not manage cookies automatically, which is why mutating requests need the antiforgery cookie + token threaded through explicitly. SparkTestClient (via CreateAuthorizedClientAsync) does this for you; if you need the raw values, call factory.MintAntiforgeryAsync().
Snapshot tests — VerifyDefaults
The module initializer configures Verify automatically, so snapshots land under VerifyResults/{TestClass}/{TestMethod}.verified.*. No per-test setup needed; just await Verify(result).
Related
- CronosCore RavenDB test helper — complementary JSON-seeding + Verify infrastructure standardized across MintPlayer repos.
- HTTP API Specification — the endpoints the
SparkEndpointFactoryhost exposes.
| 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.11)
- MintPlayer.Spark (>= 10.0.0-preview.52)
- MintPlayer.Spark.Abstractions (>= 10.0.0-preview.52)
- Newtonsoft.Json (>= 13.0.4)
- RavenDB.TestDriver (>= 7.2.5)
- Verify.Xunit (>= 31.12.5)
- xunit (>= 2.9.3)
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.0-preview.54 | 0 | 8/19/2026 |
| 10.0.0-preview.53 | 33 | 8/18/2026 |
| 10.0.0-preview.52 | 57 | 8/18/2026 |
| 10.0.0-preview.51 | 56 | 8/16/2026 |
| 10.0.0-preview.49 | 55 | 8/16/2026 |
| 10.0.0-preview.48 | 48 | 8/16/2026 |
| 10.0.0-preview.47 | 47 | 8/16/2026 |
| 10.0.0-preview.46 | 44 | 8/15/2026 |
| 10.0.0-preview.45 | 47 | 8/15/2026 |
| 10.0.0-preview.44 | 48 | 8/14/2026 |
| 10.0.0-preview.43 | 57 | 8/14/2026 |
| 10.0.0-preview.42 | 50 | 8/9/2026 |
| 10.0.0-preview.41 | 77 | 6/28/2026 |
| 10.0.0-preview.40 | 80 | 6/17/2026 |
| 10.0.0-preview.39 | 71 | 6/9/2026 |
| 10.0.0-preview.38 | 66 | 6/9/2026 |
| 10.0.0-preview.35 | 62 | 6/7/2026 |
| 10.0.0-preview.34 | 77 | 6/6/2026 |