Skip to content
Development
Skill

/snapshot-testing

Use Verify for snapshot testing in .NET. Approve API surfaces, HTTP responses, rendered emails, and serialized outputs. Detect unintended changes through human-reviewed baseline files.

From plugin
dotnet-skills
1.1k36 skills6 agents
Install
$ npx -y skills add aaronontheweb/dotnet-skills --skill snapshot-testing --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/snapshot-testing

Context preview

The summary Claude sees to decide when to auto-load this skill.

Use Verify for snapshot testing in .NET. Approve API surfaces, HTTP responses, rendered emails, and serialized outputs. Detect unintended changes through human-reviewed baseline files.

SKILL.md

snapshot-testing.SKILL.md
name: snapshot-testing
description: Use Verify for snapshot testing in .NET. Approve API surfaces, HTTP responses, rendered emails, and serialized outputs. Detect unintended changes through human-reviewed baseline files.
invocable: false

Snapshot Testing with Verify

When to Use This Skill

Use snapshot testing when:

  • Verifying rendered output (HTML emails, reports, generated code)
  • Approving public API surfaces for breaking change detection
  • Testing HTTP response bodies and headers
  • Validating serialization output
  • Catching unintended changes in complex objects

---

What is Snapshot Testing?

Snapshot testing captures output and compares it against a human-approved baseline:

1. **First run**: Test generates a `.received.` file with actual output 2. **Human review**: Developer approves it, creating a `.verified.` file 3. **Subsequent runs**: Test compares output against `.verified.` file 4. **Changes detected**: Test fails, diff tool shows differences for review

This catches **unintended changes** while allowing **intentional changes** through explicit approval.

---

Installation

Add Verify Package

dotnet add package Verify.Xunit
# or for other test frameworks:
dotnet add package Verify.NUnit
dotnet add package Verify.MSTest

Configure ModuleInitializer

Create a `ModuleInitializer.cs` in your test project:

using System.Runtime.CompilerServices;

public static class ModuleInitializer
{
    [ModuleInitializer]
    public static void Init()
    {
        // Use source-file-relative paths for verified files
        VerifyBase.UseProjectRelativeDirectory("Snapshots");

        // Configure diff tool (optional - auto-detected)
        // DiffTools.UseOrder(DiffTool.Rider, DiffTool.VisualStudioCode);
    }
}

---

Basic Usage

Simple Object Verification

[Fact]
public Task VerifyUserDto()
{
    var user = new UserDto(
        Id: "user-123",
        Name: "John Doe",
        Email: "john@example.com",
        CreatedAt: new DateTime(2025, 1, 15));

    return Verify(user);
}

Creates `VerifyUserDto.verified.txt`:

{
  Id: user-123,
  Name: John Doe,
  Email: john@example.com,
  CreatedAt: 2025-01-15T00:00:00
}

String/HTML Verification

[Fact]
public async Task VerifyRenderedEmail()
{
    var html = await _emailRenderer.RenderAsync("Welcome", new { Name = "John" });

    // Use extension parameter for proper file naming
    await Verify(html, extension: "html");
}

Creates `VerifyRenderedEmail.verified.html` - viewable in browser.

---

Email Template Testing

Use Verify to catch unintended changes in rendered email templates:

[Fact]
public async Task UserSignupInvitation_RendersCorrectly()
{
    var renderer = _services.GetRequiredService<IMjmlTemplateRenderer>();

    var variables = new Dictionary<string, string>
    {
        { "OrganizationName", "Acme Corporation" },
        { "InviteeName", "John Doe" },
        { "InviterName", "Jane Admin" },
        { "InvitationLink", "https://example.com/invite/abc123" },
        { "ExpirationDate", "December 31, 2025" }
    };

    var html = await renderer.RenderTemplateAsync(
        "UserInvitations/UserSignupInvitation",
        variables);

    await Verify(html, extension: "html");
}

**Benefits for email testing:**

  • Catches CSS/layout regressions
  • Detects broken template variables
  • Visual review in diff tool
  • Version control tracks email changes

---

API Surface Approval

Prevent accidental breaking changes to public APIs:

[Fact]
public Task ApprovePublicApi()
{
    var assembly = typeof(MyLibrary.PublicClass).Assembly;

    var publicApi = assembly.GetExportedTypes()
        .OrderBy(t => t.FullName)
        .Select(t => new
        {
            Type = t.FullName,
            Members = t.GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
                .Where(m => m.DeclaringType == t)
                .OrderBy(m => m.Name)
                .Select(m => m.ToString())
        });

    return Verify(publicApi);
}

Or use the dedicated ApiApprover package:

dotnet add package PublicApiGenerator
dotnet add package Verify.Xunit
[Fact]
public Task ApproveApi()
{
    var api = typeof(MyPublicClass).Assembly.GeneratePublicApi();
    return Verify(api);
}

Creates `.verified.txt` with full API surface - any change requires explicit approval.

---

HTTP Response Testing

[Fact]
public async Task GetUser_ReturnsExpectedResponse()
{
    var client = _factory.CreateClient();

    var response = await client.GetAsync("/api/users/123");

    // Verify status, headers, and body together
    await Verify(new
    {
        StatusCode = response.StatusCode,
        Headers = response.Headers
            .Where(h => h.Key.StartsWith("X-"))  // Custom headers only
            .ToDictionary(h => h.Key, h => h.Value.First()),
        Body = await response.Content.ReadAsStringAsync()
    });
}

---

Scrubbing Dynamic Values

Handle timestamps, GUIDs, and other dynamic content:

[Fact]
public Task VerifyOrder()
{
    var order = new Order
    {
        Id = Guid.NewGuid(),  // Different every run
        CreatedAt = DateTime.UtcNow,  // Different every run
        Total = 99.99m
    };

    return Verify(order)
        .ScrubMember("Id")  // Replace with placeholder
        .ScrubMember("CreatedAt");
}

Output:

{
  Id: Guid_1,
  CreatedAt: DateTime_1,
  Total: 99.99
}

Global Scrubbing

Configure in `ModuleInitializer`:

[ModuleInitializer]
public static void Init()
{
    VerifierSettings.ScrubMembersWithType<DateTime>();
    VerifierSettings.ScrubMembersWithType<DateTimeOffset>();
    VerifierSettings.ScrubMembersWithType<Guid>();

    // Scrub specific patterns
    VerifierSettings.AddScrubber(s =>
        Regex.Replace(s, @"token=[a-zA-Z0-9]+", "token=SCRUBBED"));
}

---

File Organizatio

Read more
Ships withdotnet-skills

A comprehensive AI coding plugin with 30 skills and 5 specialized agents for professional .NET development. Battle-tested patterns from production systems covering C#, Akka.NET, Aspire, EF Core, testing, and performance optimization.

Get the whole plugin