Skip to main content
SEMastery
Testingintermediate

Simplify Assertions in Unit and Integration Tests With Verify in .NET

A friendly .NET 10 guide to Verify snapshot testing: cut long assertions, check big objects and JSON, and keep tests clean in xUnit, NUnit, and MSTest.

13 min readUpdated November 15, 2025

The school report card analogy

Imagine the first day of a new school term. Your class teacher takes a clean photo of how your desk should look: books stacked on the left, pencil box on the right, water bottle in the corner. She pins that photo on the wall. This photo is the "correct" version.

Every morning after that, she does not measure each book with a ruler or count every pencil one by one. She just glances at your desk and compares it to the pinned photo. If your desk matches the photo, all good. If something is different, she notices at once and asks you why.

That pinned photo is doing a lot of work with very little effort. One glance replaces a long checklist.

Verify works the same way for your tests. Instead of writing twenty lines that each check one tiny field, you take one "photo" of your result. Verify pins it as the correct version. From then on, every test run compares the new result to that pinned photo. If they match, the test passes quietly. If they differ, Verify shows you exactly what changed, just like the teacher spotting the missing water bottle.

This guide shows you how to use Verify in .NET 10 to make your assertions short, clear, and easy to maintain.

What is snapshot testing?

Most tests follow a pattern called Arrange, Act, Assert. You set up some data, you run the code, and then you check the result. The checking part, the asserting, is where things get messy when the result is large.

Say your method returns an Order object with fifteen fields, and each field holds more nested objects. To check it the old way, you write something like fifteen or more Assert lines. That is a lot of typing, and it is easy to forget a field. If the shape of Order changes later, you must hunt down and fix every one of those lines.

Snapshot testing flips this around. You do not describe the result field by field. You capture the whole result as text and save it to a file. That saved file is the snapshot. On the next run, the tool serializes the result again and compares it to the saved snapshot. If they are identical, the test passes. If not, the test fails and shows the difference.

The basic idea of snapshot testing: capture once, compare every run after.

Verify is the most popular snapshot testing library in .NET. It was created by Simon Cropp and is free and open source. It plugs into the testing framework you already use, whether that is xUnit, NUnit, or MSTest.

The two files: received and verified

Verify uses two kinds of files, and understanding them is the key to everything else.

File typeEnds withWhat it meansCommit to git?
Verified.verified.txtThe approved snapshot, your source of truthYes, always
Received.received.txtThe fresh output from the latest runNo, add to .gitignore

The verified file is the photo pinned on the wall. It is the correct, approved result. You commit it to source control so every teammate and the build server use the same reference.

The received file is the new output Verify just produced. Verify only creates it when the new result does not match the verified file. It is temporary. You compare it against the verified file, and if the change is good, you promote the received file to become the new verified file.

Here is the flow of what happens on a test run.

What happens on each Verify run

Run
Compare
Decide

Steps

1

Run test

Verify serializes your result to text

2

Compare files

received text vs verified file

3

Decide outcome

pass, create snapshot, or fail with diff

Verify decides between three outcomes every time a test runs.

Getting started: install the package

Verify ships as different NuGet packages, one for each test framework. Pick the one that matches your project. For xUnit you install Verify.Xunit. For NUnit you install Verify.NUnit, and for MSTest you install Verify.MSTest.

dotnet add package Verify.Xunit

This guide uses xUnit because it is the most common choice in .NET today, but the ideas are the same for all three frameworks. The only difference is the base class or attribute you use, which we will see below.

Your first Verify test

Let's start with a small example. Suppose we have a Person class and a factory method that builds one. We want to test that the factory returns the right person.

Here is the old way, with many assertions.

public record Person(string FirstName, string LastName, int Age, string City);
 
public class PersonFactory
{
    public static Person Create() =>
        new("Asha", "Verma", 27, "Pune");
}
 
public class PersonTests
{
    [Fact]
    public void Create_old_way()
    {
        var person = PersonFactory.Create();
 
        Assert.Equal("Asha", person.FirstName);
        Assert.Equal("Verma", person.LastName);
        Assert.Equal(27, person.Age);
        Assert.Equal("Pune", person.City);
    }
}

Four fields means four asserts. Imagine fifty fields. Now here is the same test with Verify.

using VerifyXunit;
 
public class PersonVerifyTests
{
    [Fact]
    public Task Create_with_verify()
    {
        var person = PersonFactory.Create();
 
        return Verify(person);
    }
}

That is the whole test. The method returns a Task because Verify is asynchronous. The first time you run it, Verify serializes the person object and saves it to a file named PersonVerifyTests.Create_with_verify.verified.txt. Because there was nothing to compare against yet, the test fails the first time on purpose, so you can review and approve the snapshot.

The saved file looks like this, in a clean human-readable format:

{
  FirstName: Asha,
  LastName: Verma,
  Age: 27,
  City: Pune
}

Notice it is not strict JSON. Verify uses a relaxed format that is easy for humans to read and easy for diffs to show clearly. Once you approve this file, every future run compares the new output to it.

How approving a change works

The first run, or any run where the result changes, will fail and produce a .received.txt file next to the .verified.txt file. You then decide whether the change is correct.

The approve loop you follow when a snapshot changes.

You can accept a change in three easy ways:

  • Right-click the failed test in Visual Studio or Rider and choose Accept Received.
  • Use the Verify diff tool, which pops open automatically and lets you accept with one click.
  • Replace the .verified.txt file with the .received.txt content by hand, then delete the received file.

This review step is the heart of snapshot testing. You are never blindly trusting the output. You look at the diff, just like the teacher comparing the desk to the photo, and you decide if the change makes sense.

Why this is so helpful

Let's compare the two styles side by side so the benefit is clear.

ConcernManual assertionsVerify snapshot
Lines of code per testMany, one per fieldOne Verify call
Adding a new fieldEdit every testApprove the new diff
ReadabilityClutteredClean and short
Catching unexpected changesEasy to miss a fieldCatches the whole shape
Reviewing changesRead codeRead a clear text diff

The big win is that Verify checks the entire object. With manual asserts, if a new field appears and you forget to add an assert for it, your test still passes and you never notice. Verify notices, because the snapshot covers everything.

Using Verify in integration tests

This is where Verify really earns its keep. In an integration test you often call a real HTTP endpoint and get back a large JSON response. Asserting that response field by field is painful. Verify lets you snapshot the whole thing.

Here is an example using ASP.NET Core's WebApplicationFactory with xUnit. We call an endpoint like GET /orders/{id} and verify the full response body.

using System.Net.Http.Json;
using VerifyXunit;
 
public class OrdersEndpointTests
    : IClassFixture<WebApplicationFactory<Program>>
{
    private readonly HttpClient _client;
 
    public OrdersEndpointTests(WebApplicationFactory<Program> factory)
    {
        _client = factory.CreateClient();
    }
 
    [Fact]
    public async Task Get_order_returns_expected_payload()
    {
        var response = await _client.GetAsync("/orders/42");
        var body = await response.Content.ReadAsStringAsync();
 
        await Verify(body)
            .ScrubMember("requestId");
    }
}

The single Verify call captures the whole response body. We will look at ScrubMember in the next section, because real responses have parts that change every run.

Verify in an integration test

Call API
Get JSON
Snapshot

Steps

1

Call API

hit the endpoint with HttpClient

2

Read JSON

read the full response body as text

3

Verify body

compare whole payload to verified file

Snapshot the full HTTP response instead of asserting each field.

Handling values that change: scrubbing

Some values change on every test run. Think of timestamps, random GUIDs, generated IDs, or today's date. If you snapshot them as-is, your test will fail every single time because the value is never the same twice.

Verify solves this with scrubbers. A scrubber replaces a changing value with a steady placeholder before the comparison. For example, a DateTime becomes DateTime_1, and a Guid becomes Guid_1. The placeholder is stable, so the snapshot stays the same across runs.

[Fact]
public Task Order_with_changing_values()
{
    var order = new
    {
        Id = Guid.NewGuid(),
        CreatedAt = DateTime.UtcNow,
        Customer = "Ravi Kumar",
        Total = 1499.00m
    };
 
    return Verify(order);
}

Verify already knows that Guid and DateTime are usually noise, so it scrubs them by default. The snapshot becomes:

{
  Id: Guid_1,
  CreatedAt: DateTime_1,
  Customer: Ravi Kumar,
  Total: 1499.00
}

You can also scrub by member name, as we did with ScrubMember("requestId") earlier, or scrub specific text with ScrubLinesContaining. This keeps your snapshots stable while still checking everything that actually matters.

Scrubbing turns noisy values into stable placeholders before comparison.

Verify works on more than objects

Verify is not limited to C# objects. Because the comparison is really about turning something into text or bytes and comparing, it can handle many kinds of output:

  • JSON and XML strings, formatted nicely so diffs are readable.
  • HTML pages and rendered views.
  • Images, PDFs, and binary files, compared byte for byte.
  • Source generator output, which is a very popular use of Verify among library authors.
  • GraphQL responses, which tend to be large and deeply nested.

This wide reach is why teams adopt Verify across both unit and integration tests. The same mental model, capture and compare, works everywhere.

A note on test framework setup

Each framework hooks into Verify slightly differently. The table below shows the small setup difference. Once it is in place, the Verify call itself looks the same.

FrameworkNuGet packageWhat you add
xUnitVerify.XunitCall Verify(...) directly in the test
NUnitVerify.NUnitAdd the [TestFixture] and call Verify(...)
MSTestVerify.MSTestInherit from VerifyBase in the test class

A common one-time step is to enable the diff tool and configure the build so received and verified files nest nicely under your test in the IDE. Verify ships default MSBuild includes that group *.received.* and *.verified.* files under the test that produced them, which keeps your project tidy.

Good habits with Verify

A few simple rules will keep your snapshot tests healthy and trustworthy:

  • Commit verified files. They are the source of truth. If they are missing, tests fail for everyone else and on the build server.
  • Never commit received files. Add *.received.* to your .gitignore so they do not pollute pull requests.
  • Review every diff. A snapshot test is only as good as the human who approves the change. Read the diff before you accept it.
  • Scrub the noise, not the signal. Scrub timestamps and random IDs, but never scrub the real data you are trying to test.
  • Keep snapshots small where you can. Huge snapshots are hard to review. Snapshot the part of the result that matters.

When not to use Verify

Verify is wonderful, but it is not the right tool for everything. For a single simple value, like checking that 2 + 2 == 4, a plain Assert.Equal is clearer and faster. Snapshot testing is best when the result is large, structured, or tedious to assert by hand. Use the right tool for the size of the job, and mix both styles in the same project freely.

References and further reading

Quick recap

  • Snapshot testing captures your result as text or bytes and compares it to a saved, approved version on every run.
  • Verify is the most popular snapshot library in .NET, free and open source, and works with xUnit, NUnit, and MSTest.
  • A verified file (.verified.txt) is the approved snapshot you commit; a received file (.received.txt) is the fresh output you never commit.
  • One Verify call can replace many Assert lines, and it checks the whole result, so you never miss a new field.
  • Scrubbers replace changing values like GUIDs and timestamps with stable placeholders so snapshots stay steady.
  • Verify is especially powerful in integration tests for big HTTP responses, JSON, reports, and generated output.
  • Always review the diff before accepting a change, just like checking the desk against the pinned photo.

Related Posts