snapshot-testing — Claude Code skill: Verify snapshots in .NET
A skill that gets your agent writing Verify snapshot tests: output is compared against a baseline you approve by hand.
npx skills add Aaronontheweb/dotnet-skills --skill snapshot-testing -a claude-codeWhat snapshot-testing does
The skill covers snapshot testing with the Verify library in .NET projects. The first run writes a .received. file; you read it and approve it, which produces a .verified. file. Later runs compare output against that baseline and fail on any difference. Unintended changes get caught, intended ones go through explicit approval.
It ships with worked examples for several cases: rendered HTML emails, approving an assembly's public API surface to catch breaking changes, HTTP responses with status and headers, and serialization output. A section on scrubbing covers Guid and DateTime, which change on every run — those get replaced with placeholders via ScrubMember or globally in VerifierSettings. File layout, .gitignore and .gitattributes setup are covered too.
You need a package matching your test framework: Verify.Xunit, Verify.NUnit or Verify.MSTest. For API approval, PublicApiGenerator does the job. In CI the skill advises disabling the diff tool, otherwise the build sits waiting for a window to open — the check keys off the CI environment variable.
What it helps with
- approve an assembly's public API and catch a breaking change
- spot a layout regression in an HTML email after a template edit
- compare an HTTP response body and headers against a baseline
- validate serialization output for a complex object
- strip changing GUIDs and dates out of snapshots
Example prompts for the agent
- write a Verify snapshot test for this DTO
- add a test that approves the public API of this assembly
- verify the rendered email with Verify using the html extension
How to install snapshot-testing
1Skills CLI (Vercel)
npx skills add Aaronontheweb/dotnet-skills --skill snapshot-testing -a claude-code2Manually: put the files in place
~/.claude/skills/snapshot-testing/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/snapshot-testing/
1Skills CLI (Vercel)
npx skills add Aaronontheweb/dotnet-skills --skill snapshot-testing -a codex2Manually: put the files in place
.agents/skills/snapshot-testing/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add Aaronontheweb/dotnet-skills --skill snapshot-testing -a cursor2Manually: put the files in place
.cursor/skills/snapshot-testing/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install Aaronontheweb/dotnet-skills snapshot-testing2Manually: put the files in place
.github/skills/snapshot-testing/SKILL.md1Skills CLI (Vercel)
npx skills add Aaronontheweb/dotnet-skills --skill snapshot-testing -a gemini-cli2Platform CLI
gemini skills install https://github.com/Aaronontheweb/dotnet-skills --path skills/snapshot-testingManually: put the files in place
.devin/skills/snapshot-testing/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add Aaronontheweb/dotnet-skills --skill snapshot-testing -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: How to install skills in Claude Code: five ways
Questions about snapshot-testing
How do I install snapshot-testing in Claude?
Run npx skills add Aaronontheweb/dotnet-skills --skill snapshot-testing -a claude-code. The skill lands at ~/.claude/skills/snapshot-testing/SKILL.md.
What does snapshot-testing do?
It documents snapshot testing with the Verify library: a test captures actual output, you approve it as a baseline, and later runs compare against that baseline and fail when it differs.
What do I need for the skill to work?
A Verify package for your test framework — Verify.Xunit, Verify.NUnit or Verify.MSTest. For API approval you also add PublicApiGenerator.
Source text
SKILL.md · MIT
---
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.
user-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
```bash
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:
```csharp
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
```csharp
[Fact]
public Task VerifyUserDto()
{
var user = new UserDto(
Id: "user-123",
Name: "John Doe",
Email: "[email protected]",
CreatedAt: new DateTime(2025, 1, 15));
return Verify(user);
}
```
Creates `VerifyUserDto.verified.txt`:
```json
{
Id: user-123,
Name: John Doe,
Email: [email protected],
CreatedAt: 2025-01-15T00:00:00
}
```
### String/HTML Verification
```csharp
[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:
```csharp
[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:
```csharp
[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:
```bash
dotnet add package PublicApiGenerator
dotnet add package Verify.Xunit
```
```csharp
[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
```csharp
[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:
```csharp
[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:
```json
{
Id: Guid_1,
CreatedAt: DateTime_1,
Total: 99.99
}
```
### Global Scrubbing
Configure in `ModuleInitializer`:
```csharp
[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 Organization
### Recommended Structure
```
tests/
MyApp.Tests/
Snapshots/ # All verified files
EmailTests/
WelcomeEmail.verified.html
PasswordReset.verified.html
ApiTests/
GetUser.verified.txt
EmailTests.cs
ApiTests.cs
ModuleInitializer.cs
```
### .gitignore
```gitignore
# Verify - ignore received files (only commit verified)
*.received.*
```
### .gitattributes
```gitattributes
# Treat verified files as generated (collapse in PR diffs)
*.verified.txt linguist-generated=true
*.verified.html linguist-generated=true
*.verified.json linguist-generated=true
```
---
## CI/CD Integration
### Fail on Missing Verified Files
```csharp
[ModuleInitializer]
public static void Init()
{
// In CI, fail instead of launching diff tool
if (Environment.GetEnvironmentVariable("CI") == "true")
{
VerifyDiffPlex.UseDiffPlex(OutputType.Minimal);
DiffRunner.Disabled = true;
}
}
```
### GitHub Actions
```yaml
- name: Run tests
run: dotnet test
env:
CI: true
- name: Upload snapshots on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: snapshots
path: |
**/*.received.*
**/*.verified.*
```
---
## When to Use Snapshot Testing
| Scenario | Use Snapshot Testing? | Why |
|----------|----------------------|-----|
| Rendered HTML/emails | Yes | Catches visual regressions |
| API surfaces | Yes | Prevents accidental breaks |
| Serialization output | Yes | Validates wire format |
| Complex object graphs | Yes | Easier than manual assertions |
| Simple value checks | No | Use regular assertions |
| Business logic | No | Use explicit assertions |
| Performance tests | No | Use benchmarks |
---
## Best Practices
### DO
```csharp
// Use descriptive test names - they become file names
[Fact]
public Task UserRegistration_WithValidData_ReturnsConfirmation()
// Scrub dynamic values consistently
VerifierSettings.ScrubMembersWithType<Guid>();
// Use extension parameter for non-text content
await Verify(html, extension: "html");
// Keep verified files in source control
git add *.verified.*
```
### DON'T
```csharp
// Don't verify random/dynamic data without scrubbing
var order = new Order { Id = Guid.NewGuid() }; // Fails every run!
await Verify(order);
// Don't commit .received files
git add *.received.* // Wrong!
// Don't use for simple assertions
await Verify(result.Count); // Just use Assert.Equal(5, result.Count)
```
---
## Integration with MJML Email Testing
See the `mjml-email-templates` and `verify-email-snapshots` skills for the complete pattern:
1. MJML templates with `{{variable}}` placeholders
2. Render to HTML with test data
3. Snapshot test the rendered output
4. Review changes in diff tool before approving
This catches:
- Broken variable substitution
- CSS/layout regressions
- Email client compatibility issues
- Unintended content changes
---
## Resources
- **Verify GitHub**: https://github.com/VerifyTests/Verify
- **Verify.Xunit**: https://github.com/VerifyTests/Verify.Xunit
- **ApiApprover**: https://github.com/JakeGinnivan/ApiApprover
- **DiffPlex Integration**: https://github.com/VerifyTests/Verify.DiffPlex
© Aaronontheweb · MIT · Aaronontheweb/dotnet-skills