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.
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.
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 type | Ends with | What it means | Commit to git? |
|---|---|---|---|
| Verified | .verified.txt | The approved snapshot, your source of truth | Yes, always |
| Received | .received.txt | The fresh output from the latest run | No, 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
Steps
Run test
Verify serializes your result to text
Compare files
received text vs verified file
Decide outcome
pass, create snapshot, or fail with diff
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.XunitThis 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.
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.txtfile with the.received.txtcontent 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.
| Concern | Manual assertions | Verify snapshot |
|---|---|---|
| Lines of code per test | Many, one per field | One Verify call |
| Adding a new field | Edit every test | Approve the new diff |
| Readability | Cluttered | Clean and short |
| Catching unexpected changes | Easy to miss a field | Catches the whole shape |
| Reviewing changes | Read code | Read 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
Steps
Call API
hit the endpoint with HttpClient
Read JSON
read the full response body as text
Verify body
compare whole payload to verified file
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.
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.
| Framework | NuGet package | What you add |
|---|---|---|
| xUnit | Verify.Xunit | Call Verify(...) directly in the test |
| NUnit | Verify.NUnit | Add the [TestFixture] and call Verify(...) |
| MSTest | Verify.MSTest | Inherit 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.gitignoreso 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
- Verify — official GitHub repository and documentation
- Snapshot Testing in .NET with Verify — JetBrains .NET Tools Blog
- How to Perform Snapshot Testing With Verify in C# — Code Maze
- How To Simplify Assertions in Unit and Integration Tests with Verify — Anton Martyniuk
- Snapshot testing in C# with Verify — Tim Deschryver
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
Verifycall can replace manyAssertlines, 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
Creating Data-Driven Tests With xUnit (.NET 10)
A friendly .NET 10 guide to data-driven tests in xUnit: Theory, InlineData, MemberData, ClassData, strongly typed TheoryData, and xUnit v3 tips.
ASP.NET Core Integration Testing Best Practices (.NET 10)
A friendly .NET 10 guide to ASP.NET Core integration testing: WebApplicationFactory, real databases with Testcontainers, clean test isolation, and CI tips.
Testcontainers: Integration Testing Using Docker in .NET
A friendly .NET 10 guide to Testcontainers: spin up real databases in Docker for trustworthy integration tests, with xUnit, WebApplicationFactory, and clean cleanup.
Testcontainers Best Practices for .NET Integration Testing (.NET 10)
A friendly .NET 10 guide to Testcontainers: real databases in Docker, shared container fixtures, clean test isolation, faster CI, and the mistakes to avoid.
How to Test API Integrations Using WireMock.Net in .NET 10
A beginner-friendly .NET 10 guide to testing API integrations with WireMock.Net: stub HTTP responses, simulate errors and delays, and write reliable tests.
The Test Pyramid Is a Lie (And What I Do Instead) in .NET
The test pyramid says write mostly unit tests. In real .NET apps that often backfires. Here is the testing trophy and how I balance tests instead.