Verify: Snapshot Testing for C#

Verify is a snapshot tool that simplifies the assertion of complex data.

23rd Nov. 2020

When writing unit tests, asserting that a complex object is equal to what we expect can get arduous sometimes.

[Fact]
public void ToViewModel_ReturnsExpectedResult()
{
    // Arrange
    var senderId = Guid.NewGuid();
    var recipientId = Guid.NewGuid();
    var messageId = Guid.NewGuid();
    var timestamp = new DateTime(2020, 11, 23, 16, 0, 0, 0);

    var sender = new Person
    {
        Id = senderId,
        FirstName = “John”,
        LastName = “Smith”
    };

    var recipient = new Person
    {
        Id = recipientId,
        FirstName = “Jane”,
        LastName = “Smith”
    };

    var message = new Message
    {
        Id = messageId,
        Timestamp = timestamp,
        Text = “Fancy having pasta for dinner?”,
        Sender = senderId,
        Recipient = recipientId
    };

    var expected = new MessageViewModel
    {
        Id = messageId,
        Timestamp = timestamp,
        Text = “Fancy having pasta for dinner?”,
        Sender = “John Smith”,
        Recipient = “Jane Smith”
    };

    // Act
    var actual = message.ToViewModel(sender, recipient);

    // Assert
    Assert.Equal(expected.Id, actual.Id);
    Assert.Equal(expected.Timestamp, actual.Timestamp);
    Assert.Equal(expected.Text, actual.Text);
    Assert.Equal(expected.Sender, actual.Sender);
    Assert.Equal(expected.Recipient, actual.Recipient);
}

The short example above will quickly get out of hand the more complex our resulting objects get and the more tests we add. There is also the possibility of forgetting to update assertions for any new properties that get added.

This is where Verify could come in. Much like Jest’s snapshot testing feature, Verify serialises the objects you pass and uses that initial output as the basis of future runs of tests.

[Fact]
public async Task ToViewModel_ReturnsExpectedResult_VerifyTests()
{
    // 

    // Assert
    await Verifier.Verify(actual);
}

As you can see from the snippet, the assertion is much more terse. Verify instead generated a text file with the serialised values of actual and it looks like this:

{
  Id: Guid_1,
  Sender: John Smith,
  Recipient: Jane Smith,
  Text: Fancy having pasta for dinner tonight?,
  Timestamp: DateTime_1
}

The initial run of a unit test will always fail, this is because you will be comparing against a file that does not exist yet or is empty.

Extra Settings

Lets say you want to ensure that any new properties that get added will be used as part of the assertion, you can configure it like so.

[Fact]
public async Task ToViewModel_ReturnsExpectedResult_VerifyTests()
{
    // 

    // Assert
    await Verifier.Verify(actual)
        .AddExtraSettings(_ =>
        {
            // This will only apply to this test
            _.DefaultValueHandling = DefaultValueHandling.Include;
        });
}

Have a look at the possible Serializer settings here.

Conclusion

Verify allows us to focus on writing functionality rather than writing assertions. By leveraging techniques available to front-end unit tests, we can simplify assertions within out C# unit tests.

Sample code is available in this repo.