Snapshot Testing in C#
Snapshot testing is a technique that involves capturing and comparing the output of a piece of code to a previously captured output.
In this post I’ll show how snapshot testing can be used to write a test which asserts that JSON is correctly desterilized to a class. The classes I’ll be working with are:
public class WeatherForecasts : List<WeatherForecast>
{
public static List<WeatherForecast> FromJson(string json)
{
var weatherForecast = JsonSerializer.Deserialize<List<WeatherForecast>>
(json, new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
});
if (weatherForecast is null)
{
// TODO:: Throw exception or handle negative case however you want. I'll throw for demo purpose
throw new Exception($"Cannot deserialize input json to {nameof(WeatherForecast)} record.");
}
return weatherForecast;
}
}
public record WeatherForecast(long Date, int TemperatureC, string? Summary)
{
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
Consider how you would write a test for the method FromJson
in the WeatherForecasts
class. A typical approahc would be to
- Setup the test data/json.
- Invoke the
FromJson
method. - Asset that all the properties in the instance match the expected values.
This would be an example using the XUnit framework.
public class WeatherForecastsTests
{
[Fact]
public void Given_Valid_Weather_Json_When_Converting_To_A_Instance_Of_WeatherForecasts_Then_Deserialization_Should_Succeed_And_All_Properties_Should_Be_Set1()
{
// arrange
var json = @"[
{
""date"": 1707368400,
""temperatureC"": 32,
""summary"": ""Scorching""
}
]";
// act
var forecasts = WeatherForecasts.FromJson(json);
// assert
Assert.NotNull(forecasts);
Assert.Single(forecasts);
Assert.Equal(1707368400, forecasts.First().Date);
Assert.Equal(32, forecasts.First().TemperatureC);
Assert.Equal("Scorching", forecasts.First().Summary);
Assert.Equal(89, forecasts.First().TemperatureF);
}
}
While this test certainly works it does have some drawbacks:
- For complex objects the test can take a considerable amount of time to write.
- New properties can be added to the class without causing the test to fail. Ideally any change to the class which can impact deserialization should result in a failed test.
Thankfully the Verify.Xunit
nuget package can help us address the these issues while also simplifying the test.
Using the verify nuget the test can be rewritten as:
[Fact]
public Task Given_Valid_Weather_Json_When_Converting_To_A_Instance_Of_WeatherForecasts_Then_Deserialization_Should_Succeed_And_All_Properties_Should_Be_Set()
{
// arrange
var json = @"[
{
""date"": 1707368400,
""temperatureC"": 32,
""summary"": ""Scorching""
}
]";
// act
var forecasts = WeatherForecasts.FromJson(json);
// assert
return Verify(forecasts);
}
The only change is that the method now returns Task
instead of void
and the asserts section is replaced with return Verify(forecasts);
When the test is executed for the first time it will fail, two text files will be created and your IDEs compare window will open which compares the two files
The text files created will follow the naming convention CLASS_NAME.METHOD_NAME.received.txt
and CLASS_NAME.METHOD_NAME.verified.txt
*.received.txt contains the output of the last test run and *.verified.txt contains the expected snapshot.
In order for the test to pass the content of both received and verified must match so after confirming that received has the correct input you can copy the content to verified.txt and rerun the test
Once the test passes the file *.received.txt
should have automatically deleted leaving only *.verified.txt
As a best practice files that end with *.received.txt
should not be committed to source control and *.verified.txt
must be committed otherwise the test will fail for other developers or when executed during a build pipeline.
Conclusion
Snapshot testing in a great tool which helps you focus on output structure and reduces the complexity of assertions, snapshot testing enhances the overall testing strategy, making it more efficient, readable, and accurate.
Consider incorporating snapshot testing into your testing arsenal to experience the benefits it brings to your development workflow.