In this article, we are going to discuss how to perform snapshot testing on objects using the Verify library.
Testing is an important part of our software development pipeline. As developers, we often rely on familiar testing approaches like unit testing, where we assert the behavior of individual components of an application, and integration testing, where we combine components and verify the application’s overall behavior.
However, using these approaches to test complex objects can be challenging as there is the risk of overlooking newly added components of these objects. This challenge can be handled with another known testing approach called snapshot testing.
Let’s begin.
What Is Snapshot Testing?
Snapshot testing is a software testing technique that allows us to verify the state of objects. It works by taking a snapshot of an object and storing it as a binary artifact, such as a file, which serves as the expected result. During each test execution, the current state of the object is compared to this expected result. If they match, the test passes. Otherwise, the test fails. We can find this approach very useful when we want to assert the content of files, API responses, UI component output, logs, and more.
In .NET, we can utilize the Verify library created by Simon Cropp to implement a snapshot test in our application. This library simplifies the process of capturing, storing, and comparing snapshots by providing a seamless integration with popular testing frameworks like xUnit, NUnit, and others.
Verify supports different object types, including text files, JSON, images, and custom objects, making it suitable for a wide range of testing scenarios. Additionally, Verify automatically manages the storage of our snapshots and offers intuitive failure messages when they do not match.
How Does Verify Perform Snapshot Testing?
When we use Verify to implement snapshot tests in our applications, the first execution of our test will fail. Verify will then take a snapshot of our model, store it in a file, and prompt us to accept the snapshot if it correctly represents our object, or reject if it does not.
After this initial setup, on subsequent runs of our test, if there are no changes to our object, the test will always pass. However, if we make any change to our model, the test will fail again, and Verify will prompt us to accept or reject the latest model snapshot.
Let’s see this in action.
Using Verify With xUnit
To see Verify in action with xUnit, let’s first create a sample object that we want to test.
For this, let’s imagine we have an API endpoint that returns a Vehicle
:
public class Vehicle { public Guid Id { get; set; } public string? Make { get; set; } public string? Model { get; set; } public int Year { get; set; } public string? Color { get; set; } public Address? Location { get; set; } public List<string>? Features { get; set; } } public record Address(string Street, string City, string State, string Country);
Next, let’s create a method that returns a Vehicle
object:
public static Vehicle GetVehicle() => new() { Id = new("ebced679-45d3-4653-8791-3d969c4a986c"), Make = "Toyota", Model = "Camry", Year = 2022, Color = "Blue", Location = new Address("123 Main St", "Anytown", "CA", "USA"), Features = [ "Sunroof", "4 Seats", "Navigation" ], };
Here, we define a GetVehicle()
method that creates and returns a Vehicle
object. We will use verify to snapshot test this endpoint, to ensure it always returns the expected object.
Implement the Snapshot Test
To implement our test, first, we have to create an xUnit test project:
dotnet new xunit -n Tests
Then, we need to install the Verify package:
dotnet add package Verify.Xunit
Once we have installed the package, we can define our test:
public class VerifyVehicleTests { [Fact] public Task WhenGetVehicleIsCalled_ThenReturnsTheCorrectObject() { var vehicle = VehicleFactory.GetVehicle(); return Verify(vehicle); } }
We first call the GetVehicle()
method to return the vehicle object that we want to test. Then we invoke the Verify()
method on this returned object to check if it is correctly serialized and matches the previously stored snapshot.
Now, on the first execution of this test, it will fail because we are comparing our model with an empty file. At this point, Verify generates a WhenGetVehicleIsCalled_ThenReturnsTheCorrectObject.received.txt
file:
{ Id: Guid_1, Make: Toyota, Model: Camry, Year: 2022, Color: Blue, Location: { Street: 123 Main St, City: Anytown, State: CA, Country: USA }, Features: [ Sunroof, 4 Seats, Navigation ] }
This file contains the serialized values of our object. After generating this file, Verify will prompt us to inspect and accept our model’s snapshot.
There are several techniques that we can use to accept a snapshot. In this article, we simply copy the snapshot from the received file and paste it into the verified file.
Now, once we accept the snapshot and save the file our test will pass on subsequent runs. But if we make changes to the state of our object, the next execution of our test will fail. Verify will then prompt us to accept or reject the latest snapshot.
So, that’s it!
Setting up and using Verify to assert the state of objects in our apps is a quick and straightforward process. With the Verify package, we can focus on implementing and testing functionalities efficiently rather than writing assertions for complex objects. Always remember that correctly managing and accepting snapshot files is crucial to ensuring that our tests are successful.
Conclusion
In this article, we have seen that snapshot tests are an effective way of catching changes to complex objects in our applications. We started by discussing what snapshot testing is and how it works. Then, we used the Verify package to implement a snapshot test and saw how to inspect and accept snapshots.
With snapshot tests, we can accurately detect and manage changes in our codebase, thereby ensuring the stability of our applications.