Asynchronous values

edit

Snapshot.Validate reads its value synchronously. A Stream that only supports asynchronous reads makes it fail, and an IAsyncEnumerable<T> is enumerated by blocking the test until every item is produced. Snapshot.ValidateAsync reads both asynchronously and accepts a CancellationToken. It takes the same type and settings as Snapshot.Validate, and the snapshot gets the same name.

Validate a stream

The stream is read with CopyToAsync and stored as its bytes:

await using var stream = new MemoryStream("Hello, world!"u8.ToArray());

// The stream is read with CopyToAsync, from its current position, and is neither rewound nor disposed
await Snapshot.ValidateAsync(stream, TestContext.Current.CancellationToken); // __snapshots__/SnapshotTests_DocumentationExample_ValidateAsync.verified.txt
  • The stream is read from its current position to its end. It is neither rewound nor disposed: a stream you have just written to must be rewound first, or the snapshot is empty.
  • Once read, the content is validated like a Stream passed to Snapshot.Validate, so a serializer registered for streams still applies.

Validate an asynchronous sequence

The sequence is enumerated with await foreach, and the items are written like a list:

static async IAsyncEnumerable<string> GetNamesAsync()
{
    foreach (var name in new[] { "John", "Jane" })
    {
        await Task.Yield(); // For instance, a page of results from a database or a web API
        yield return name;
    }
}

// The sequence is enumerated with await foreach instead of blocking the test
await Snapshot.ValidateAsync(GetNamesAsync(), TestContext.Current.CancellationToken); // __snapshots__/SnapshotTests_DocumentationExample_ValidateAsyncEnumerable.verified.txt
  • The snapshot is the same as the one Snapshot.Validate writes for the sequence, including for a sequence of KeyValuePair<string, T>, which is written like a dictionary.
  • The serializers receive the items as a List<T>, so a serializer registered for IAsyncEnumerable<T> does not apply.
  • Only the value you pass is enumerated asynchronously. An IAsyncEnumerable<T> in a property of an object is still enumerated by blocking.

Completion and cancellation

  • The value is read into memory before anything else happens. No snapshot file is read or written before the stream is fully read or the sequence fully enumerated.
  • The task completes after the comparison and the update of the snapshot files, and fails with the same exception as Snapshot.Validate when the snapshot differs.
  • Canceling the token while the value is read throws an OperationCanceledException and leaves the snapshot files untouched.