Snapshooter is a snapshot testing tool for .NET Core and .NET Framework
Snapshooter is a flexible snapshot testing tool to simplify the result validation in your unit tests in .Net. It is based on the idea of Jest Snapshot Testing.
To get more detailed information about Snapshooter, go to the Snapshooter Docs
Getting Started
To get started, install the Snapshooter Xunit or NUnit nuget package:
XUnit
dotnet add package Snapshooter.Xunit
NUnit
dotnet add package Snapshooter.NUnit
MSTest
dotnet add package Snapshooter.MSTest
Get Started
Assert with Snapshots
To assert your test results with snapshots in your unit tests, follow the following steps:
1. Add snapshot assert statement
Insert a snapshot assert statement Snapshot.Match(yourResultObject);
into your unit test.
Example:
[Fact]
public void CreatePersonSnapshotTest()
{
var serviceClient = new ServiceClient();
TestPerson person = serviceClient.CreatePerson(
Guid.Parse("2292F21C-8501-4771-A070-C79C7C7EF451"), "David", "Mustermann");
Snapshot.Match(person);
}
2. Run the unit test to create a new Snapshot
The Snapshot.Match(person)
statement creates a new snapshot of your result object and stores it in the
__snapshots__
folder. The __snapshots__
folder is always next to your executed unit test file.
Snapshot name: <UnitTestClassName>.<TestMethodName>.snap
3. Review new snapshot
Review your new snapshot file __snapshots__/<UnitTestClassName>.<TestMethodName>.snap
.
4. Run unit test to assert
Now the Snapshot.Match(person)
statement will create again a snapshot of your test result and compare it against your reviewed snapshot in the __snapshots__
folder. The __snapshots__
folder is always next to your executed unit test file.
Mismatching Snapshot Handling
If your result object has changed and the existing snapshot is not matching anymore, then the unit test will fail. The unit test error message will point to the exact mismatching position within the snapshot.
In addition, in the snapshot folder __snapshots__
a subfolder with name __mismatch__
will be created. In this folder you can find
the actual snapshot which is mismatching with the existing snapshot in the __snapshots__
folder. Therefore it is possible to compare the two snapshots with a text compare tool.
If the snapshot in the mismatching folder __mismatch__
is correct, just move it to the parent __snapshots__
folder (override the existing one).
Read More
Different Match-Syntax Possibilities
The default match syntax for snapshots is:
Snapshot.Match(person);
However, we also could use the fluent syntax:
person.MatchSnapshot();
Or we can use FluentAssertion's should() syntax:
person.Should().MatchSnapshot();
For NUnit we will support the Assert.That syntax (Coming soon):
Assert.That(person, Match.Snapshot());
Features
Ignore Fields in Snapshots
If some fields in your snapshot shall be ignored during snapshot assertion, then the following ignore options can be used:
[Fact]
public void CreatePersonSnapshot_IgnoreId()
{
var serviceClient = new ServiceClient();
TestPerson person = serviceClient.CreatePerson("Hans", "Muster");
Snapshot.Match<Person>(testPerson, matchOptions => matchOptions.IgnoreField("Size"));
}
The fields to ignore will be located via JsonPath, therefore you are very flexible and you can also ignore fields from child objects or arrays.
Ignore Examples:
Snapshot.Match<Person>(person, matchOptions => matchOptions.IgnoreField("Address.StreetNumber"));
Snapshot.Match<Person>(person, matchOptions => matchOptions.IgnoreField("Address.Country.Name"));
Snapshot.Match<Person>(person, matchOptions => matchOptions.IgnoreField("Relatives[0].Id"));
Snapshot.Match<Person>(person, matchOptions => matchOptions.IgnoreField("Children[*].Name"));
Snapshot.Match<Person>(person, matchOptions => matchOptions.IgnoreField("**.Id"));
Ignore All Fields by name
If we want to ignore all fields by a specific name, then we have two options:
Option 1: Use the ignore match option 'IgnoreAllFields()' and add the name.
Snapshot.Match<Person>(person, matchOptions => matchOptions.IgnoreAllFields("Id"));
Option 2: Use the default ignore match option 'IgnoreFields(**.)' with the following JsonPath syntax **.
Snapshot.Match<Person>(person, matchOptions => matchOptions.IgnoreFields("**.Id"));
Hash Fields in Snapshots
If some fields of our snapshot are too big, for example a binary field with a lot of data, then we can use the HashField option.
The HashField option creates a Hash of the field value and therefore each time only the hash is compared.
If there is a change in the field value, then the snapshot match will fail.
[Fact]
public void ImageSnapshot_HashImageBinary()
{
var serviceClient = new ServiceClient();
TestImage image = serviceClient.CreateMonaLisaImage();
Snapshot.Match(image, matchOptions => matchOptions.HashField("Data"));
}
Example Snapshot with Hash
{
"Id": 3450987,
"OwnerId": "0680faef-6e89-4d52-bad8-291053c66696",
"Name": "Mona Lisa",
"CreationDate": "2020-11-10T21:23:09.036+01:00",
"Price": 951868484.345,
"Data": "m+sQR9KG9WpgYoQiRASPkt9FLJOLsjK86UuiXKVRzas="
}
The field(s) to hash can be located via JsonPath or via field name.
Hash Field Examples:
Snapshot.Match<Person>(person, matchOptions => matchOptions.HashField("Thumbnail.Data"));
Snapshot.Match<Person>(person, matchOptions => matchOptions.HashField("Thumbnails[0].Data"));
Snapshot.Match<Person>(person, matchOptions => matchOptions.HashField("Thumbnails[*].Data"));
Snapshot.Match<Person>(person, matchOptions => matchOptions.HashField("**.Data"));
Assert Fields in Snapshots
Sometimes there are fields in a snapshot, which you want to assert separately against another value.
For Example, the Id field of a 'Person' is always newly generated in a service,
therefore you receive in the test always a Person with a new id (Guid).
Now if you want to check that the id is not an empty Guid, the Assert
option can be used.
[Fact]
public void CreatePersonSnapshot_AssertId()
{
var serviceClient = new ServiceClient();
TestPerson person = serviceClient.CreatePerson("Hans", "Muster");
Snapshot.Match<Person>(testPerson, matchOption => matchOption.Assert(
fieldOption => Assert.NotEqual(Guid.Empty, fieldOption.Field<Guid>("Id"))));
}
The fields to assert will be located via JsonPath, therefore you are very flexible and you can also ignore fields from child objects or arrays.
Assert Examples:
Snapshot.Match<Person>(person, matchOption => matchOption.Assert(
fieldOption => Assert.Equal(15, fieldOption.Field<int>("Address.StreetNumber"))));
Snapshot.Match<Person>(person, matchOption => matchOption.Assert(
fieldOption => Assert.Equal("De", fieldOption.Field<CountryCode>("Address.Country.Code"))));
Snapshot.Match<Person>(person, > matchOption.Assert(
fieldOption => Assert.NotNull(fieldOption.Field<string>("Relatives[0].Id"))));
Snapshot.Match<Person>(person, > matchOption.Assert(
fieldOption => Assert.NotNull(fieldOption.Fields<string>("Relatives[*].Id"))));
Snapshot.Match<Person>(person, > matchOption.Assert(
fieldOption => Assert.NotNull(fieldOption.Fields<TestPerson>("Relatives[*]"))));
The Snapshooter assert functionality is not limited to Xunit or NUnit asserts, it also could be used Fluent Assertions or another assert tool.
Concatenate Ignore & Asserts checks
All the ignore, isType or assert field checks can be concatenated.
[Fact]
public void Match_ConcatenateFieldChecksTest_SuccessfulMatch()
{
var serviceClient = new ServiceClient();
TestPerson person = serviceClient.CreatePerson("Hans", "Muster");
Snapshot.Match<TestPerson>(testPerson, matchOption => matchOption
.Assert(option => Assert.NotEqual(Guid.Empty, option.Field<Guid>("Id")))
.IgnoreField<DateTime>("CreationDate")
.Assert(option => Assert.Equal(-58, option.Field<int>("Address.StreetNumber")))
.Assert(option => testChild.Should().BeEquivalentTo(option.Field<TestChild>("Children[3]")))
.IgnoreField<TestCountry>("Address.Country")
.Assert(option => Assert.Null(option.Field<TestCountry>("Relatives[0].Address.Plz"))));
}
Using Snapshooter in CI-Builds
When running snapshooter tests in a CI-build you might want to ensure that a snapshots are correctly checked-in since otherwise tests without a snapshot will just create the initial snapshot and become green.
In order to fail tests that are without a snapshot on your CI-build you can set the snapshooter behavior to strict-mode by setting the environment variable SNAPSHOOTER_STRICT_MODE
to on
or true
.
This project has adopted the code of conduct defined by the Contributor Covenant
to clarify expected behavior in our community. For more information, see the Swiss Life OSS Code of Conduct.