Simple snapshot testing with inspiration from amazing Jest library.
You can install it using Nuget.
If you are unfamiliar with snapshot testing, I recommend you to check Jest documentation.
This library works very similarly. When you run ShouldMatchSnapshot
for the first time, it will generate JSON snapshot of the serialization of the object.
If you run it second time, it will check if the JSON serialization of the object is the same as the saved JSON snapshot.
It is important to commit snapshots
with your code to the git.
You can mass update snapshot (useful when you add new property to an object, change default value or remove a property).
Simply set environment variable UPDATE
to true
and run the tests that you want to update. It will update all snapshots that have failed.
If you are using JetBrains Rider, go to Settings -> Build, Execution, Deployment -> Unit Testing -> Test Runner
and set it there.
Remember to unset the variable again.
Snapshots are not created if you are running tests inside Continuous Integration environment (Gitlab CI, Jenkins, Teamcity, Azure/AWS Pipelines etc.).
Library detects CI environment by check CI
environment variable. If it is set to true
, test fails if snapshot is missing.
public static void ShouldMatchSnapshot(this object actual, string hint = "");
var person = new Person
{
Age = 13,
DateOfBirth = new DateTime(2008, 7, 7),
FirstName = "John",
LastName = "Bam"
};
person.ShouldMatchSnapshot();
public static void ShouldMatchInlineSnapshot(this object actual, string inlineSnapshot);
var person = new Person
{
Age = 13,
DateOfBirth = new DateTime(2008, 7, 7),
FirstName = "John",
LastName = "Bam"
};
person.ShouldMatchInlineSnapshot(@"
{
""FirstName"": ""John"",
""LastName"": ""Bam"",
""DateOfBirth"": ""2008-07-07T00:00:00"",
""Age"": 13,
}"
);
public static void ShouldMatchObject(this object actual, object expected);
var actual = new Person
{
Age = 13,
DateOfBirth = new DateTime(2008, 7, 7),
FirstName = "John",
LastName = "Bam"
};
var expected = new Person
{
Age = 13,
DateOfBirth = new DateTime(2008, 7, 7),
FirstName = "John",
LastName = "Bam"
};
actual.ShouldMatchObject(expected);
If you don't like extension methods, you can use static class JestAssert
public static void ShouldMatchSnapshot(object actual, string hint = "");
var person = new Person
{
Age = 13,
DateOfBirth = new DateTime(2008, 7, 7),
FirstName = "John",
LastName = "Bam"
};
JestAssert.ShouldMatchSnapshot(person);
public static void ShouldMatchInlineSnapshot(object actual, string inlineSnapshot);
var person = new Person
{
Age = 13,
DateOfBirth = new DateTime(2008, 7, 7),
FirstName = "John",
LastName = "Bam"
};
JestAssert.ShouldMatchInlineSnapshot(person, @"
{
""FirstName"": ""John"",
""LastName"": ""Bam"",
""DateOfBirth"": ""2008-07-07T00:00:00"",
""Age"": 13,
}"
);
public static void ShouldMatchObject(object actual, object expected);
var actual = new Person
{
Age = 13,
DateOfBirth = new DateTime(2008, 7, 7),
FirstName = "John",
LastName = "Bam"
};
var expected = new Person
{
Age = 13,
DateOfBirth = new DateTime(2008, 7, 7),
FirstName = "John",
LastName = "Bam"
};
JestAssert.ShouldMatchObject(actual,expected);
If you want to exclude some properties from the diff, you can use SnapshotSettings
class to specify your own
- diffing options (use
SnapshotSettings.CreateDiffOptions
)
Example:
SnapshotSettings.CreateDiffOptions = () => new JsonDiffOptions
{
PropertyFilter = (s, context) => s != "LastName"
};
or pass JsonDiffOptions
as optional argument
var actual = new Person
{
Age = 13,
DateOfBirth = new DateTime(2008, 7, 7),
FirstName = "John",
LastName = "Bam"
};
var expected = new Person
{
Age = 13,
DateOfBirth = new DateTime(2008, 7, 7),
FirstName = "John",
LastName = ""
};
// this does not throw an exception and the test completes successfully
// property "LastName" is ignored from the diff
JestAssert.ShouldMatchObject(actual,expected, new JsonDiffOptions
{
PropertyFilter = (s, context) => s != "LastName"
});
If you need to configure it, you can use SnapshotSettings
class to specify your own
- extension instead of
.snap
(useSnapshotSettings.SnapshotExtension
) - directory instead of
__snapshots__
(useSnapshotSettings.SnapshotDirectory
) - function that generates directory, extension and filename (use
SnapshotSettings.CreatePath
)
Popular use is to change directory of the snapshot files. You can do it like this:
SnapshotSettings.SnapshotDirectory = "__custom__";
var testObject = new Person
{
Age = 13,
DateOfBirth = new DateTime(2008, 7, 7),
FirstName = "John",
LastName = "Bam"
};
JestAssert.ShouldMatchSnapshot(testObject);
// you can return it back using
SnapshotSettings.SnapshotDirectory = SnapshotSettings.DefaultSnapshotDirectory;
For serialization, I am using Json.NET. If you need to configure it, you can use SnapshotSettings
class to specify your own
JsonSerializer
(useSnapshotSettings.CreateJsonSerializer
)JTokenWriter
(useSnapshotSettings.CreateJTokenWriter
)StringWriter
(useSnapshotSettings.CreateStringWriter
)JsonTextWriter
(useSnapshotSettings.CreateJsonTextWriter
).
Popular use is to change line ending of the .snap
files. For example if you want to set line ending to Linux LF
, you can do it like this:
SnapshotSettings.CreateStringWriter = () => new StringWriter(CultureInfo.InvariantCulture)
{
NewLine = "\n"
};
var testObject = new Person
{
Age = 13,
DateOfBirth = new DateTime(2008, 7, 7),
FirstName = "John",
LastName = "Bam"
};
JestAssert.ShouldMatchSnapshot(testObject);
SnapshotSettings
expects you define your own function that returns new configured instance.
SnapshotSettings.CreateJsonSerializer = () =>
{
var serializer = SnapshotSettings.DefaultCreateJsonSerializer();
serializer.ContractResolver = new AlphabeticalPropertySortContractResolver();
return serializer;
};
var testObject = new Person
{
Age = 13,
DateOfBirth = new DateTime(2008, 7, 7),
FirstName = "John",
LastName = "Bam"
};
JestAssert.ShouldMatchSnapshot(testObject);
For Newtonsoft.Json, the resolver can look something like this
public sealed class AlphabeticalPropertySortContractResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var properties = base.CreateProperties(type, memberSerialization);
return properties.OrderBy(p => p.PropertyName).ToList();
}
}
You cannot call neither extension nor JestAssert
with dynamic
object. You need to cast it to object
(or real type).