XBullet.EasyTesting.Snapshots.Core 1.0.13

There is a newer version of this package available.
See the version list below for details.
dotnet add package XBullet.EasyTesting.Snapshots.Core --version 1.0.13
                    
NuGet\Install-Package XBullet.EasyTesting.Snapshots.Core -Version 1.0.13
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="XBullet.EasyTesting.Snapshots.Core" Version="1.0.13" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="XBullet.EasyTesting.Snapshots.Core" Version="1.0.13" />
                    
Directory.Packages.props
<PackageReference Include="XBullet.EasyTesting.Snapshots.Core" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add XBullet.EasyTesting.Snapshots.Core --version 1.0.13
                    
#r "nuget: XBullet.EasyTesting.Snapshots.Core, 1.0.13"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package XBullet.EasyTesting.Snapshots.Core@1.0.13
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=XBullet.EasyTesting.Snapshots.Core&version=1.0.13
                    
Install as a Cake Addin
#tool nuget:?package=XBullet.EasyTesting.Snapshots.Core&version=1.0.13
                    
Install as a Cake Tool

XBullet.EasyTesting.Snapshots.Core

Lightweight, framework-independent JSON and text snapshot assertions for HTTP responses and arbitrary values. This package does not depend on XBullet.EasyTesting, ASP.NET testing, or XBullet.EasyTesting.Http.

The package targets .NET 8, .NET 9, and .NET 10.

Install

dotnet add package XBullet.EasyTesting.Snapshots.Core

Use XBullet.EasyTesting.Snapshots.Http for snapshots of outbound requests captured by StubHttpMessageHandler. The original XBullet.EasyTesting.Snapshots package remains available as a compatibility facade that references both packages.

Example

var settings = new SnapshotSettings()
    .Named("administrator-order")
    .ScrubMembers("Id", "CreatedAt")
    .ScrubGuids();

await SnapshotAssert.MatchAsync(result, settings);

For a centralized or test-specific layout, resolve the directory from the calling context:

var settings = new SnapshotSettings()
    .InDirectory(context => Path.Combine(
        context.SourceDirectory,
        "snapshots",
        context.SourceFileName));

Configuration and workflows

Snapshot locations

Snapshots are stored in a __snapshots__ directory beside the calling source file by default. Choose another directory with InDirectory; relative paths are resolved from the calling source file rather than the process working directory. To keep snapshots directly beside the source file, use BesideSourceFile:

var settings = new SnapshotSettings()
    .BesideSourceFile();

await SnapshotAssert.MatchAsync(result, settings);

Raw JSON

Raw JSON can be verified as structured JSON instead of as an escaped string. Raw strings and HTTP content are parsed and normalized with System.Text.Json. Verified files use *.verified.json; received files include the current target framework, such as *.received.net8.0.json, so multi-targeted test runs cannot overwrite each other's failures.

var json = $$"""
    {
      "orderId": 42,
      "status": "ready",
      "correlationId": "{{Guid.NewGuid()}}"
    }
    """;
var settings = new SnapshotSettings()
    .ScrubGuids();

await SnapshotAssert.MatchJsonAsync(json, settings);

The resulting verified snapshot contains normalized JSON:

{
  "orderId": 42,
  "status": "ready",
  "correlationId": "{Guid}"
}

Invalid JSON throws JsonException without creating a snapshot.

Plain text

Use MatchTextAsync when the content should not be parsed or serialized as JSON. Text snapshots use .verified.txt and runtime-qualified .received.*.txt files. Custom string scrubbers still apply:

var settings = new SnapshotSettings()
    .Scrub(text => text.Replace(secret, "{Redacted}", StringComparison.Ordinal));

await SnapshotAssert.MatchTextAsync(commandOutput, settings);

HTTP JSON content

Verify only the JSON response body when status, headers, and request metadata do not belong in the snapshot:

using var response = await client.GetAsync("/api/orders/42");
response.EnsureSuccessStatusCode();

await response.ShouldMatchJsonBodySnapshot();

response.Content.ShouldMatchJsonSnapshot() is also available when only the HttpContent is in scope. For buffered or seekable content, both assertions rewind the body and restore its original position, so they remain reliable after the body has already been read.

Use response.ShouldMatchControllerSnapshot() instead when the snapshot should also contain the request method and URL, response status, and stable headers. Sensitive and volatile headers, including Set-Cookie, Authentication-Info, Proxy-Authentication-Info, Date, and tracing identifiers, are excluded by default. Header capture can be customized without exposing values:

var options = new ControllerSnapshotOptions()
    .RedactingHeaders("Set-Cookie", "X-Session-Token")
    .RedactingQueryParameter("tenant_secret")
    .ScrubbingUrlPathGuids()
    .ScrubbingQueryParameters("timestamp", "requestId");

await response.ShouldMatchControllerSnapshot(options);

Redacted headers and query values are captured as {Redacted}. Common secret-bearing query names, including access_token, api_key, client_secret, sas, secret, sig, and token, are redacted by default. Call WithoutHeaders() to omit the entire header collection, or IncludingHeader(name) and IncludingQueryParameter(name) to explicitly include a value known to be safe.

URL scrubbing keeps the stable route while replacing volatile data. ScrubbingUrlPathGuids() replaces complete GUID path segments with {Guid}, and ScrubbingQueryParameters(...) replaces configured query values with {Scrubbed}. Use ScrubbingUrlPath(path => ...) for custom route transformations. The same URL APIs are available on HTTP exchange request options. Redaction takes precedence when the same query name is also configured for scrubbing.

Complete HTTP exchanges

Use HttpExchangeRecorder when a snapshot must contain the complete request and response, including the request body. It is a delegating handler, not a stub, so the request still reaches the real server or ASP.NET Core TestServer. With XBullet's test-client builder:

var exchangeOptions = new HttpExchangeSnapshotOptions();
exchangeOptions.Response.IgnoringHeaders("Location");

var recorder = new HttpExchangeRecorder(exchangeOptions);
using var client = scope.Client()
    .AsUser(user => user.WithName("snapshot tester"))
    .WithHandler(recorder)
    .Build();

using var response = await client.PostAsJsonAsync(
    "/api/products",
    new { name = "Webcam", price = 79.95m });

await response.ShouldMatchHttpExchangeSnapshot(
    snapshotSettings: new SnapshotSettings().ScrubMember("id"));

The recorder captures requests before transport consumption, captures response metadata before returning it, and records response content as the caller reads it. This preserves ResponseHeadersRead and streaming behavior. An unread response body appears as {NotRead}, while a content-read error is recorded as BodyFailure without being misclassified as a send failure. JSON is stored structurally; text stays text; binary bodies use base64. Authorization, cookies, API keys, XBullet's test identity and client-certificate transport headers, correlation identifiers, and tracing headers are excluded by default. Configure request and response headers independently through HttpExchangeSnapshotOptions. A test factory or client helper can attach a fresh recorder to each client, so individual tests only need the response extension shown above.

Without a recorder, the same response extension falls back to HttpResponseMessage.RequestMessage. That is sufficient when its content remains readable; attach a recorder for TestServer and other pipelines that may consume or replace the request content. Use recorder.ShouldMatchHttpExchangesSnapshot() when one snapshot should contain every call made by the client.

JSON remains the default snapshot format. Set HttpExchangeSnapshotOptions.Format to HttpExchangeSnapshotFormat.Http for an HTTP-style .verified.txt transcript, or to HttpExchangeSnapshotFormat.Yaml for .verified.yaml. Structured JSON scrubbers run before the selected renderer.

Multiple snapshots and parameterized tests

Use a variant when one test method produces multiple snapshots or when each parameterized case needs its own file:

var settings = new SnapshotSettings()
    .ForVariant($"status-{statusCode}");

await SnapshotAssert.MatchAsync(result, settings);

The variant is appended to the test-derived snapshot name. Snapshot names and variants are encoded portably, automatically shortened with a stable hash when necessary, and produce the same safe filename on Windows and Linux. When parameter text should never appear in the filename, use ForHashedVariant(parameters).

Targeted JSON transformations

Use extended JSON Pointer rules when a member name should only be transformed at a specific path:

var settings = new SnapshotSettings()
    .ScrubPath("/orders/*/id")
    .IgnorePath("/orders/*/generatedAt")
    .ReplacePath("/environment", "test")
    .HashPath("/largePayload")
    .SortArray("/orders", "/id")
    .CanonicalizeJson();

await SnapshotAssert.MatchJsonAsync(json, settings);

Paths are case-sensitive. An empty path selects the root, / separates segments, and * selects every member or array item at one level. Escape ~ as ~0, / as ~1, and a literal * member as ~2. Missing paths are ignored. Array sort keys are compared by their canonical JSON text; array order remains unchanged unless SortArray is configured.

HashPath writes a stable sha256:... marker based on canonical JSON. It is useful for reducing large values while still detecting changes, but it is not a substitute for removing secrets with IgnorePath.

CanonicalizeJson sorts object properties recursively while preserving array order. ScrubDateTimes only matches ISO-8601 round-trip timestamps. Custom string scrubbers must return valid JSON.

Diagnostics and safe maintenance

Mismatches identify the first structural difference using JSONPath and expose its values on SnapshotMismatchException:

var exception = await Assert.ThrowsAsync<SnapshotMismatchException>(
    () => SnapshotAssert.MatchAsync(result));

Assert.Equal("$.orders[0].status", exception.DifferencePath);
Console.WriteLine($"{exception.ExpectedValue} -> {exception.ActualValue}");

Configure project-wide snapshot, controller-capture, and HTTP-exchange defaults once before test discovery by adding a module initializer to the test project:

using System.Runtime.CompilerServices;
using XBullet.EasyTesting.Snapshots;

internal static class SnapshotConfiguration
{
    [ModuleInitializer]
    internal static void Initialize()
    {
        SnapshotSettingsDefaults.Global = new(settings => settings
            .BesideSourceFile()
            .ScrubGuids()
            .ScrubDateTimes()
            .ScrubMembers("RequestId", "CorrelationId")
            .IgnoreMembers("AccessToken")
            .CanonicalizeJson()
            .WithoutDiffTool());

        ControllerSnapshotOptionsDefaults.Global = new(options => options
            .IgnoringHeaders("ETag", "X-Request-Nonce")
            .RedactingHeaders("Authorization", "X-Session-Token"));

        HttpExchangeSnapshotOptionsDefaults.Global = new(options =>
        {
            options.Format = HttpExchangeSnapshotFormat.Yaml;
            options.Request.IgnoringHeaders("X-Request-Nonce");
            options.Response.IgnoringHeaders("ETag");
        });
    }
}

Assertions use the global template directly when snapshotSettings is omitted:

await SnapshotAssert.MatchAsync(result);

using var response = await client.GetAsync("/api/products");
await response.ShouldMatchHttpExchangeSnapshot();

Each assertion receives an independent settings copy. Explicit settings are merged into the global template: scrubbers, member rules, and path rules are combined, while locally configured scalar values such as the name or directory take precedence. Therefore this keeps global scrubbers and adds the local one:

await response.ShouldMatchHttpExchangeSnapshot(
    snapshotSettings: new SnapshotSettings().ScrubMember("timestamp"));

ExtendGlobal also accepts an action when a configured settings object is useful before calling the assertion:

var settings = SnapshotSettingsDefaults.ExtendGlobal(settings => settings
    .Named("products")
    .ForVariant($"case-{caseId}"));

await response.ShouldMatchHttpExchangeSnapshot(snapshotSettings: settings);

Controller options also merge with their global template. Header and query-parameter decisions are combined, and an explicit local decision wins for the same name:

await response.ShouldMatchControllerSnapshot(
    controllerOptions: new ControllerSnapshotOptions()
        .IgnoringHeaders("Location")
        .IncludingHeader("ETag"));

HTTP exchange options merge the same way. Use HttpExchangeSnapshotOptionsDefaults.ExtendGlobal(...) to create a configured independent copy for an HttpExchangeRecorder or an individual assertion.

Configure each Global once before tests start. Assign null to restore package defaults.

Track the snapshots exercised by a complete test scope to find obsolete verified files. A catalog can remain explicit and instance-scoped so parallel projects do not share its observed-file state:

var catalog = new SnapshotCatalog();
var defaults = new SnapshotSettingsDefaults(settings => settings
    .ScrubGuids()
    .TrackingWith(catalog));

await SnapshotAssert.MatchAsync(result, defaults.Create());

// Run only after every snapshot in this catalog's scope has executed.
var obsolete = catalog.FindObsoleteSnapshots(snapshotDirectory);

Maintenance is preview-first and requires explicit confirmation:

var received = SnapshotMaintenance.FindReceivedSnapshots(snapshotDirectory);
var accepted = SnapshotMaintenance.AcceptReceivedSnapshots(
    snapshotDirectory,
    confirmed: true);
var removed = SnapshotMaintenance.RemoveVerifiedSnapshots(
    obsolete,
    confirmed: true);

Review received and obsolete before changing files. Removal validates every supplied path as a verified snapshot file before deleting any of them.

Automatic update modes remain disabled in CI unless separately authorized. Set INTEGRATION_TESTS_ALLOW_SNAPSHOT_UPDATES_IN_CI=true or call AllowingUpdatesInContinuousIntegration() in addition to selecting missing or all update mode. Keep this opt-in limited to dedicated snapshot-update jobs.

The first run writes a received snapshot. Review and approve it as the verified snapshot; subsequent runs report structural differences. Update modes and local diff viewers are opt-in.

Documentation

Product 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 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

    • No dependencies.
  • net8.0

    • No dependencies.
  • net9.0

    • No dependencies.

NuGet packages (3)

Showing the top 3 NuGet packages that depend on XBullet.EasyTesting.Snapshots.Core:

Package Downloads
XBullet.EasyTesting.Snapshots

Compatibility facade for the split snapshot core and outbound HTTP adapter packages.

XBullet.EasyTesting.Verify.Xunit

Verify.XunitV3 snapshot support for XBullet.EasyTesting controller responses.

XBullet.EasyTesting.Snapshots.Http

Snapshot adapters for XBullet.EasyTesting HTTP stubs and hosted scenario exchanges.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.17 0 10/1/2026
1.0.16 34 9/30/2026
1.0.15 75 9/29/2026
1.0.14 118 9/27/2026
1.0.13 124 9/26/2026
1.0.12 123 9/25/2026
1.0.11 124 9/24/2026
1.0.9 122 9/24/2026
1.0.8 136 9/21/2026
1.0.7 133 9/19/2026
1.0.6 134 9/18/2026
1.0.5 129 9/18/2026