Verify: Snapshot Testing for C#

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

23 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.

Similar Posts

Cypress Part I: Getting Started

In this new series, we look at how we could include Cypress as part of our testing strategy for our single page applications.

15 Nov 20195-minute read

Cypress Part II: Configuration

Now that we have added Cypress to our existing SPA, let's have a look at overriding the default configurations.

04 Dec 20192-minute read

Typescript 3.7

The new version of TypeScript has been released and with it comes optional chaining as well as nullish coalescing!

05 Nov 20191-minute read