Snapshot Testing in .NET with Verify

Writing assertions for complex objects is tedious. When your API returns a response with twenty fields, writing twenty Assert.Equal calls is not only verbose — it's fragile and hard to maintain. Snapshot testing takes a different approach: capture the output once, save it to a file, and then verify future runs produce the same result.

Verify is the leading snapshot testing library for .NET. It works with xUnit, NUnit, and MSTest, and handles everything from JSON objects to rendered HTML to images.

Getting Started

Example.cs
dotnet add package Verify.Xunit

You also need to add a ModuleInitializer to configure Verify's settings globally:

Example.cs
public static class ModuleInit
{
    [ModuleInitializer]
    public static void Init() =>
        VerifySourceGenerators.Initialize();
}

Your First Snapshot Test

Example.cs
[UsesVerify]
public class UserServiceTests
{
    [Fact]
    public Task GetUserProfile_ReturnsExpectedShape()
    {
        var profile = new UserProfile
        {
            Id = 1,
            Name = "Alice Smith",
            Email = "[email protected]",
            Roles = new[] { "Admin", "Editor" },
            CreatedAt = new DateTime(2024, 1, 15, 10, 30, 0, DateTimeKind.Utc)
        };

        return Verify(profile);
    }
}

The first time you run this test, it will fail and create a "received" file: GetUserProfile_ReturnsExpectedShape.received.txt. The file contains the serialised object:

data.json
{
  Id: 1,
  Name: Alice Smith,
  Email: [email protected],
  Roles: [
    Admin,
    Editor
  ],
  CreatedAt: DateTime_1
}

Review the output, and if it's correct, accept it. This creates the "verified" file that becomes your baseline. Subsequent runs compare against this snapshot.

Handling Dynamic Values

Dates, GUIDs, and timestamps change between test runs. Verify has scrubbers to handle this:

Example.cs
[Fact]
public Task CreateOrder_ReturnsOrderWithGeneratedId()
{
    var order = _service.CreateOrder("Widget", 5);

    return Verify(order)
        .ScrubMember("Id")
        .ScrubMember("CreatedAt");
}

Scrubbed values are replaced with stable placeholders like Guid_1 or DateTime_1, so your snapshots remain deterministic.

You can also configure scrubbers globally:

Example.cs
[ModuleInitializer]
public static void Init()
{
    VerifierSettings.ScrubMembers("Id", "CreatedAt", "UpdatedAt");
}

Snapshot Testing API Responses

Verify integrates beautifully with WebApplicationFactory for testing API responses:

Example.cs
[UsesVerify]
public class ProductApiTests : IClassFixture<WebApplicationFactory<Program>>
{
    private readonly HttpClient _client;

    public ProductApiTests(WebApplicationFactory<Program> factory)
    {
        _client = factory.CreateClient();
    }

    [Fact]
    public async Task GetProducts_ReturnsExpectedJson()
    {
        var response = await _client.GetAsync("/api/products");
        var content = await response.Content.ReadAsStringAsync();

        await Verify(content);
    }
}

If someone changes the API response shape — adds a field, removes a field, or changes a type — the snapshot test will fail with a clear diff showing exactly what changed.

Verifying Collections

Example.cs
[Fact]
public Task GetActiveUsers_ReturnsExpectedList()
{
    var users = _repository.GetActiveUsers();

    return Verify(users)
        .ScrubMember("LastLoginAt");
}

Verify serialises the entire collection, so you can see every element in the snapshot. If the ordering matters, the snapshot captures that too.

Working with the Diff Tool

When a snapshot test fails, Verify can automatically open a diff tool so you can see what changed side-by-side. It supports most popular diff tools out of the box:

Example.cs
// In your ModuleInitializer
[ModuleInitializer]
public static void Init()
{
    DiffTools.UseOrder(DiffTool.VisualStudioCode, DiffTool.Rider);
}

When the test fails, your diff tool opens with the received output alongside the verified snapshot. If the change is intentional, accept the new snapshot. If not, you've caught a regression.

CI Considerations

In CI environments, you don't want diff tools opening. Verify detects CI automatically and will simply fail the test with a descriptive message. The received file is generated as a build artifact so you can inspect it.

Make sure your verified snapshot files (*.verified.txt) are committed to source control. They are your test expectations.

When to Use Snapshot Testing

Snapshot testing shines for:

It's less suitable for testing simple values or behaviour that's better expressed as a direct assertion. Assert.Equal(5, result) is clearer than a snapshot containing just the number 5.

Verify is a powerful addition to your testing toolkit. It complements traditional assertions rather than replacing them, handling the cases where manual assertions become unwieldy.