String formats and source files

edit

When the library writes a snapshot, it edits the source file of the test: it replaces the expected string of the call, or adds it when it is missing. These settings control how.

String formats

AllowedStringFormats lists the C# string literals the library may write, as CSharpStringFormats flags:

FlagLiteral
Quoted"Name: meziantou", for a single line without escape sequences
Verbatim@"...", for several lines, backslashes or quotes
RawA raw string literal, """...""", indented like the calling code (C# 11 or later)
LeftAlignedRawA raw string literal whose content and closing quotes start at the first column of the file
DetermineFeatureFromPdbReads the C# version of the project from the PDB, and does not use a literal it does not support. Requires a portable PDB

The default, CSharpStringFormats.Default, is Quoted | Verbatim | Raw | DetermineFeatureFromPdb: a quoted string for a simple single line, and for several lines a raw string when the project uses C# 11 or later, a verbatim string otherwise.

InlineSnapshot
    .WithSettings(settings => settings.AllowedStringFormats = CSharpStringFormats.Quoted | CSharpStringFormats.Verbatim | CSharpStringFormats.Raw)
    .Validate(new { Name = "meziantou" }, "Name: meziantou");

With Raw only, even a single line is written as a raw string:

InlineSnapshot
    .WithSettings(settings => settings.AllowedStringFormats = CSharpStringFormats.Raw)
    .Validate(new object(), """
        {}
        """);

With LeftAlignedRaw, the content and the closing quotes of the snapshot start at the first column of the file, whatever the indentation of the calling code:

        InlineSnapshot
            .WithSettings(settings => settings.AllowedStringFormats = CSharpStringFormats.LeftAlignedRaw)
            .Validate(new object(), """
{}
""");

Values a format cannot write

Only a quoted string can escape characters. The library writes the value with the first allowed format that keeps it:

  • Invisible characters (control and format characters, such as an ANSI escape sequence or a zero-width space) and the Unicode newline characters (\u0085, \u2028, \u2029) are written as escape sequences in a quoted string when Quoted is allowed.
  • Unpaired UTF-16 surrogates cannot be stored by any file encoding. Without Quoted, the update fails with an InlineSnapshotException and the source file is not changed.
  • A raw string is not exact. Its lines end with the line ending of the source file, and the library leaves the lines that contain only whitespace empty. The library uses a raw string only when the SnapshotComparer considers its compiled value equal to the actual value. With SnapshotComparer.Default, which ignores these differences, a multi-line value is always a raw string. With SnapshotComparer.Ordinal, a value with whitespace-only lines is written as a verbatim string, and a value whose line endings differ from the ones of the file (\r\n in a file that uses \n, or a lone \r) as a quoted string with escape sequences: git and editors may convert the line endings of the file, so a verbatim string cannot keep them. When none of the allowed formats can keep the value, the update fails and asks you to allow Quoted.

Indentation, line endings and encoding

By default, the indentation, the line endings and the encoding are detected from the source file, so an edit looks like the code around it. Set Indentation, EndOfLine and FileEncoding to choose them:

InlineSnapshot
    .WithSettings(settings =>
    {
        settings.Indentation = "    ";
        settings.EndOfLine = "\r\n";
        settings.FileEncoding = Encoding.UTF8;
    })
    .Validate(new { Name = "meziantou" }, "Name: meziantou");

How the call is found

The library finds the call to update from the [CallerFilePath] and [CallerLineNumber] that the compiler passes to Validate, and parses the source file to find the expected argument of that call.

  • The source file must be on disk. The tests must run where the source code is, from a build of that code.
  • The PDB is used to check the location. By default (ValidateSourceFilePathUsingPdbInfoWhenAvailable), the file is checked against the call stack read from the PDB, and the update fails when they differ, which may happen with a Release build. Set the property to false to skip the check. ValidateLineNumberUsingPdbInfoWhenAvailable, disabled by default, also checks the line. The PDB gives the line where the expression starts, and [CallerLineNumber] the line of the call, which differ when the expression spans several lines.
  • Deterministic paths are mapped back to the disk. See Deterministic source paths.
  • The expected argument must still hold the old value. If the source file changed since the build, so that the argument no longer contains the expected value the test ran with, the update fails instead of editing the wrong code.
  • The call must be in the member that made it. A call on the recorded line is only updated when it is in the member, and the type, that made the call. When the file changed since the build so that the line holds the call of another member, the library searches the calling member instead. When several members cannot be told apart, such as overloads with as many parameters, and more than one of them holds the old snapshot, the update fails: rebuild the project and run the test again.
  • Several test processes can update the same file. A test project that targets several frameworks runs one process per framework: the processes take turns to edit a file.

When the call cannot be updated, the test fails with the reason and the snapshot difference, so you can copy the value yourself.

A helper method that calls Validate must say which of its parameters holds the snapshot and forward the caller information. See Helper methods.