Skip to content

Latest commit

 

History

History
1862 lines (1559 loc) · 55.3 KB

serializer-settings.md

File metadata and controls

1862 lines (1559 loc) · 55.3 KB

Serializer settings

Verify uses Argon for serialization. See Default Settings for on how Argon is used and instructions on how to control that usage.

Serialization settings can be customized at three levels:

  • Method: Will run the verification in the current test method.
  • Class: Will run for all verifications in all test methods for a test class.
  • Global: Will run for test methods on all tests.

Not valid json

Note that the output is technically not valid json.

  • Names and values are not quoted.
  • Newlines are not escaped.

The reason for these is that it makes approval files cleaner and easier to read and visualize/understand differences.

UseStrictJson

To use strict json call VerifierSettings.UseStrictJson:

Globally

[ModuleInitializer]
public static void Init() =>
    VerifierSettings.UseStrictJson();

snippet source | anchor

Instance

var target = new TheTarget
{
    Value = "Foo"
};
var settings = new VerifySettings();
settings.UseStrictJson();
await Verify(target, settings);

snippet source | anchor

Fluent

var target = new TheTarget
{
    Value = "Foo"
};
await Verify(target)
    .UseStrictJson();

snippet source | anchor

Result

Then this results in

  • The default .received. and .verified. extensions for serialized verification to be .json.
  • JsonTextWriter.QuoteChar to be ".
  • JsonTextWriter.QuoteName to be true.

Then when an object is verified:

var target = new TheTarget
{
    Value = "Foo"
};
await Verify(target);

snippet source | anchor

The resulting file will be:

{
  "Value": "Foo"
}

snippet source | anchor

UseUtf8NoBom

The default encoding for snapshot files uses UTF-8 with byte order marks (BOM) enable. To disable UTF-8 BOMs, call VerifierSettings.UseUtf8NoBom.

public static class ModuleInitializer
{
    [ModuleInitializer]
    public static void Init() =>
        VerifierSettings.UseUtf8NoBom();
}

snippet source | anchor

UseEncoding

To override the encoding used for snapshot files, replacing the default UTF-8 encoding, call VerifierSettings.UseEncoding providing a System.Text.Encoding instance.

public static class ModuleInitializer
{
    [ModuleInitializer]
    public static void Init()
    {
        var encoding = new UnicodeEncoding(
            bigEndian: false,
            byteOrderMark: true,
            throwOnInvalidBytes: true);
        VerifierSettings.UseEncoding(encoding);
    }
}

snippet source | anchor

Default settings

Verify uses Argon for serialization.

Argon is a JSON framework for .NET. It is a hard fork of Newtonsoft.Json.

See Argon documentation

The default JsonSerializerSettings are:

var settings = new JsonSerializerSettings
{
    Formatting = Formatting.Indented,
    ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
    DefaultValueHandling = DefaultValueHandling.Ignore
};

snippet source | anchor

Modify Defaults

Globally

VerifierSettings
    .AddExtraSettings(_ =>
        _.TypeNameHandling = TypeNameHandling.All);

snippet source | anchor

Instance

[Fact]
public Task AddExtraSettings()
{
    var settings = new VerifySettings();
    settings
        .AddExtraSettings(
            _ => _.SerializeError = (currentObject, originalObject, location, member, exception, handled) =>
                Console.WriteLine(member));
    return Verify("Value", settings);
}

snippet source | anchor

Fluent

[Fact]
public Task AddExtraSettingsFluent() =>
    Verify("Value")
        .AddExtraSettings(
            _ => _.SerializeError = (currentObject, originalObject, location, member, exception, handled) =>
                Console.WriteLine(member));

snippet source | anchor

QuoteName is false

JsonTextWriter.QuoteName is set to false. The reason for this is that it makes approval files cleaner and easier to read and visualize/understand differences.

Empty collections are ignored

By default empty collections are ignored during verification.

To disable this behavior globally use:

VerifierSettings.DontIgnoreEmptyCollections();

snippet source | anchor

Changing Json.NET settings

Extra Json.NET settings can be made:

Globally

VerifierSettings.AddExtraSettings(
    _ => _.TypeNameHandling = TypeNameHandling.All);

snippet source | anchor

Instance

var settings = new VerifySettings();
settings.AddExtraSettings(
    _ => _.TypeNameHandling = TypeNameHandling.All);

snippet source | anchor

Json.NET Converter

One common use case is to register a custom JsonConverter. As only writing is required, to help with this there is WriteOnlyJsonConverter, and WriteOnlyJsonConverter<T>.

class CompanyConverter :
    WriteOnlyJsonConverter<Company>
{
    public override void Write(VerifyJsonWriter writer, Company company) =>
        writer.WriteMember(company, company.Name, "Name");
}

snippet source | anchor

VerifierSettings.AddExtraSettings(
    _ => _.Converters.Add(new CompanyConverter()));

snippet source | anchor

VerifyJsonWriter

VerifyJsonWriter exposes the following members:

  • Counter property that gives programmatic access to the counting behavior used by Guid, Date, and Id scrubbing.
  • Serializer property that exposes the current JsonSerializer.
  • Serialize(object value) is a convenience method that calls JsonSerializer.Serialize passing in the writer instance and the value parameter.
  • WriteProperty<T, TMember>(T target, TMember value, string name) method that writes a property name and value while respecting other custom serialization settings eg member converters, ignore rules etc.

Scoped settings

[Fact]
public Task ScopedSerializer()
{
    var person = new Person
    {
        GivenNames = "John",
        FamilyName = "Smith"
    };
    var settings = new VerifySettings();
    settings.AddExtraSettings(_ => _.TypeNameHandling = TypeNameHandling.All);
    return Verify(person, settings);
}

[Fact]
public Task ScopedSerializerFluent()
{
    var person = new Person
    {
        GivenNames = "John",
        FamilyName = "Smith"
    };
    return Verify(person)
        .AddExtraSettings(_ => _.TypeNameHandling = TypeNameHandling.All);
}

snippet source | anchor

Result:

{
  $type: VerifyObjectSamples.Person,
  GivenNames: John,
  FamilyName: Smith
}

snippet source | anchor

Ignoring a type

To ignore all members that match a certain type:

[Fact]
public Task IgnoreType()
{
    var target = new IgnoreTypeTarget
    {
        ToIgnore = new()
        {
            Property = "Value"
        },
        ToIgnoreNullable = new()
        {
            Property = "Value"
        },
        ToIgnoreByInterface = new()
        {
            Property = "Value"
        },
        ToIgnoreByBase = new()
        {
            Property = "Value"
        },
        ToIgnoreByBaseGeneric = new()
        {
            Property = "Value"
        },
        ToIgnoreByType = new()
        {
            Property = "Value"
        },
        ToInclude = new()
        {
            Property = "Value"
        },
        ToIncludeNullable = new()
        {
            Property = "Value"
        },
        ToIgnoreStruct = new("Value"),
        ToIgnoreStructNullable = new("Value"),
        ToIncludeStruct = new("Value"),
        ToIncludeStructNullable = new("Value")
    };
    var settings = new VerifySettings();
    settings.IgnoreMembersWithType<ToIgnore>();
    settings.IgnoreMembersWithType<ToIgnoreByType>();
    settings.IgnoreMembersWithType<InterfaceToIgnore>();
    settings.IgnoreMembersWithType<BaseToIgnore>();
    settings.IgnoreMembersWithType(typeof(BaseToIgnoreGeneric<>));
    settings.IgnoreMembersWithType<ToIgnoreStruct>();
    return Verify(target, settings);
}

[Fact]
public Task IgnoreTypeFluent()
{
    var target = new IgnoreTypeTarget
    {
        ToIgnore = new()
        {
            Property = "Value"
        },
        ToIgnoreNullable = new()
        {
            Property = "Value"
        },
        ToIgnoreByInterface = new()
        {
            Property = "Value"
        },
        ToIgnoreByBase = new()
        {
            Property = "Value"
        },
        ToIgnoreByBaseGeneric = new()
        {
            Property = "Value"
        },
        ToIgnoreByType = new()
        {
            Property = "Value"
        },
        ToInclude = new()
        {
            Property = "Value"
        },
        ToIncludeNullable = new()
        {
            Property = "Value"
        },
        ToIgnoreStruct = new("Value"),
        ToIgnoreStructNullable = new("Value"),
        ToIncludeStruct = new("Value"),
        ToIncludeStructNullable = new("Value")
    };
    return Verify(target)
        .IgnoreMembersWithType<ToIgnore>()
        .IgnoreMembersWithType<ToIgnoreByType>()
        .IgnoreMembersWithType<InterfaceToIgnore>()
        .IgnoreMembersWithType<BaseToIgnore>()
        .IgnoreMembersWithType(typeof(BaseToIgnoreGeneric<>))
        .IgnoreMembersWithType<ToIgnoreStruct>();
}

snippet source | anchor

Or globally:

VerifierSettings.IgnoreMembersWithType<ToIgnore>();

snippet source | anchor

Result:

{
  ToInclude: {
    Property: Value
  },
  ToIncludeNullable: {
    Property: Value
  },
  ToIncludeStruct: {
    Property: Value
  },
  ToIncludeStructNullable: {
    Property: Value
  }
}

snippet source | anchor

Scrub a type

To scrub all members that match a certain type:

[Fact]
public Task ScrubType()
{
    var target = new IgnoreTypeTarget
    {
        ToIgnore = new()
        {
            Property = "Value"
        },
        ToIgnoreNullable = new()
        {
            Property = "Value"
        },
        ToIgnoreByInterface = new()
        {
            Property = "Value"
        },
        ToIgnoreByBase = new()
        {
            Property = "Value"
        },
        ToIgnoreByBaseGeneric = new()
        {
            Property = "Value"
        },
        ToIgnoreByType = new()
        {
            Property = "Value"
        },
        ToInclude = new()
        {
            Property = "Value"
        },
        ToIncludeNullable = new()
        {
            Property = "Value"
        },
        ToIgnoreStruct = new("Value"),
        ToIgnoreStructNullable = new("Value"),
        ToIncludeStruct = new("Value"),
        ToIncludeStructNullable = new("Value")
    };
    var settings = new VerifySettings();
    settings.ScrubMembersWithType<ToIgnore>();
    settings.ScrubMembersWithType<ToIgnoreByType>();
    settings.ScrubMembersWithType<InterfaceToIgnore>();
    settings.ScrubMembersWithType<BaseToIgnore>();
    settings.ScrubMembersWithType(typeof(BaseToIgnoreGeneric<>));
    settings.ScrubMembersWithType<ToIgnoreStruct>();
    return Verify(target, settings);
}

[Fact]
public Task ScrubTypeFluent()
{
    var target = new IgnoreTypeTarget
    {
        ToIgnore = new()
        {
            Property = "Value"
        },
        ToIgnoreNullable = new()
        {
            Property = "Value"
        },
        ToIgnoreByInterface = new()
        {
            Property = "Value"
        },
        ToIgnoreByBase = new()
        {
            Property = "Value"
        },
        ToIgnoreByBaseGeneric = new()
        {
            Property = "Value"
        },
        ToIgnoreByType = new()
        {
            Property = "Value"
        },
        ToInclude = new()
        {
            Property = "Value"
        },
        ToIncludeNullable = new()
        {
            Property = "Value"
        },
        ToIgnoreStruct = new("Value"),
        ToIgnoreStructNullable = new("Value"),
        ToIncludeStruct = new("Value"),
        ToIncludeStructNullable = new("Value")
    };
    return Verify(target)
        .ScrubMembersWithType<ToIgnore>()
        .ScrubMembersWithType<ToIgnoreByType>()
        .ScrubMembersWithType<InterfaceToIgnore>()
        .ScrubMembersWithType<BaseToIgnore>()
        .ScrubMembersWithType(typeof(BaseToIgnoreGeneric<>))
        .ScrubMembersWithType<ToIgnoreStruct>();
}

snippet source | anchor

Or globally:

VerifierSettings.ScrubMembersWithType<ToIgnore>();

snippet source | anchor

Result:

{
  ToIgnore: {Scrubbed},
  ToIgnoreByType: {Scrubbed},
  ToIgnoreByInterface: {Scrubbed},
  ToIgnoreByBase: {Scrubbed},
  ToIgnoreByBaseGeneric: {Scrubbed},
  ToIgnoreNullable: {Scrubbed},
  ToIgnoreStruct: {Scrubbed},
  ToIgnoreStructNullable: {Scrubbed},
  ToInclude: {
    Property: Value
  },
  ToIncludeNullable: {
    Property: Value
  },
  ToIncludeStruct: {
    Property: Value
  },
  ToIncludeStructNullable: {
    Property: Value
  }
}

snippet source | anchor

Ignoring an instance

To ignore instances of a type based on delegate:

[Fact]
public Task AddIgnoreInstance()
{
    var target = new IgnoreInstanceTarget
    {
        ToIgnore = new()
        {
            Property = "Ignore"
        },
        ToInclude = new()
        {
            Property = "Include"
        }
    };
    var settings = new VerifySettings();
    settings.IgnoreInstance<Instance>(_ => _.Property == "Ignore");
    return Verify(target, settings);
}

[Fact]
public Task AddIgnoreInstanceFluent()
{
    var target = new IgnoreInstanceTarget
    {
        ToIgnore = new()
        {
            Property = "Ignore"
        },
        ToInclude = new()
        {
            Property = "Include"
        }
    };
    return Verify(target)
        .IgnoreInstance<Instance>(_ => _.Property == "Ignore");
}

snippet source | anchor

Or globally:

VerifierSettings.IgnoreInstance<Instance>(_ => _.Property == "Ignore");

snippet source | anchor

Result:

{
  ToInclude: {
    Property: Include
  }
}

snippet source | anchor

Scrub a instance

To scrub instances of a type based on delegate:

[Fact]
public Task AddScrubInstance()
{
    var target = new IgnoreInstanceTarget
    {
        ToIgnore = new()
        {
            Property = "Ignore"
        },
        ToInclude = new()
        {
            Property = "Include"
        }
    };
    var settings = new VerifySettings();
    settings.ScrubInstance<Instance>(_ => _.Property == "Ignore");
    return Verify(target, settings);
}

[Fact]
public Task AddScrubInstanceFluent()
{
    var target = new IgnoreInstanceTarget
    {
        ToIgnore = new()
        {
            Property = "Ignore"
        },
        ToInclude = new()
        {
            Property = "Include"
        }
    };
    return Verify(target)
        .ScrubInstance<Instance>(_ => _.Property == "Ignore");
}

snippet source | anchor

Or globally:

VerifierSettings.ScrubInstance<Instance>(_ => _.Property == "Ignore");

snippet source | anchor

Result:

{
  ToIgnore: {Scrubbed},
  ToInclude: {
    Property: Include
  }
}

snippet source | anchor

Obsolete members ignored

Members with an ObsoleteAttribute are ignored:

class WithObsolete
{
    [Obsolete]
    public string ObsoleteProperty { get; set; }

    public string OtherProperty { get; set; }
}

[Fact]
public Task WithObsoleteProp()
{
    var target = new WithObsolete
    {
        ObsoleteProperty = "value1",
        OtherProperty = "value2"
    };
    return Verify(target);
}

snippet source | anchor

Result:

{
  OtherProperty: value2
}

snippet source | anchor

Including Obsolete members

Obsolete members can be included using IncludeObsoletes:

[Fact]
public Task WithObsoletePropIncluded()
{
    var target = new WithObsolete
    {
        ObsoleteProperty = "value1",
        OtherProperty = "value2"
    };
    var settings = new VerifySettings();
    settings.IncludeObsoletes();
    return Verify(target, settings);
}

[Fact]
public Task WithObsoletePropIncludedFluent()
{
    var target = new WithObsolete
    {
        ObsoleteProperty = "value1",
        OtherProperty = "value2"
    };
    return Verify(target)
        .IncludeObsoletes();
}

snippet source | anchor

Or globally:

VerifierSettings.IncludeObsoletes();

snippet source | anchor

Result:

{
  ObsoleteProperty: value1,
  OtherProperty: value2
}

snippet source | anchor

Ignore member by expressions

To ignore members of a certain type using an expression:

[Fact]
public Task IgnoreMemberByExpression()
{
    var target = new IgnoreExplicitTarget
    {
        Include = "Value",
        Field = "Value",
        Property = "Value",
        PropertyWithPropertyName = "Value"
    };
    var settings = new VerifySettings();
    settings.IgnoreMembers<IgnoreExplicitTarget>(
        _ => _.Property,
        _ => _.PropertyWithPropertyName,
        _ => _.Field,
        _ => _.GetOnlyProperty,
        _ => _.PropertyThatThrows);
    return Verify(target, settings);
}

[Fact]
public Task IgnoreMemberByExpressionFluent()
{
    var target = new IgnoreExplicitTarget
    {
        Include = "Value",
        Field = "Value",
        Property = "Value"
    };
    return Verify(target)
        .IgnoreMembers<IgnoreExplicitTarget>(
            _ => _.Property,
            _ => _.Field,
            _ => _.GetOnlyProperty,
            _ => _.PropertyThatThrows);
}

snippet source | anchor

Or globally

VerifierSettings.IgnoreMembers<IgnoreExplicitTarget>(
    _ => _.Property,
    _ => _.PropertyWithPropertyName,
    _ => _.Field,
    _ => _.GetOnlyProperty,
    _ => _.PropertyThatThrows);

snippet source | anchor

Result:

{
  Include: Value
}

snippet source | anchor

Scrub member by expressions

To scrub members of a certain type using an expression:

[Fact]
public Task ScrubMemberByExpression()
{
    var target = new IgnoreExplicitTarget
    {
        Include = "Value",
        Field = "Value",
        Property = "Value",
        PropertyWithPropertyName = "Value"
    };
    var settings = new VerifySettings();
    settings.ScrubMembers<IgnoreExplicitTarget>(
        _ => _.Property,
        _ => _.PropertyWithPropertyName,
        _ => _.Field,
        _ => _.GetOnlyProperty,
        _ => _.PropertyThatThrows);
    return Verify(target, settings);
}

[Fact]
public Task ScrubMemberByExpressionFluent()
{
    var target = new IgnoreExplicitTarget
    {
        Include = "Value",
        Field = "Value",
        Property = "Value"
    };
    return Verify(target)
        .ScrubMembers<IgnoreExplicitTarget>(
            _ => _.Property,
            _ => _.Field,
            _ => _.GetOnlyProperty,
            _ => _.PropertyThatThrows);
}

snippet source | anchor

Or globally

VerifierSettings.ScrubMembers<IgnoreExplicitTarget>(
    _ => _.Property,
    _ => _.PropertyWithPropertyName,
    _ => _.Field,
    _ => _.GetOnlyProperty,
    _ => _.PropertyThatThrows);

snippet source | anchor

Result:

{
  Include: Value,
  Field: {Scrubbed},
  Property: {Scrubbed},
  _Custom: {Scrubbed},
  GetOnlyProperty: {Scrubbed},
  PropertyThatThrows: {Scrubbed}
}

snippet source | anchor

Ignore member by name

To ignore members of a certain type using type and name:

[Fact]
public Task IgnoreMemberByName()
{
    var target = new IgnoreExplicitTarget
    {
        Include = "Value",
        Field = "Value",
        Property = "Value",
        PropertyByName = "Value"
    };
    var settings = new VerifySettings();

    // For all types
    settings.IgnoreMember("PropertyByName");

    // For a specific type
    settings.IgnoreMember(typeof(IgnoreExplicitTarget), "Property");

    // For a specific type generic
    settings.IgnoreMember<IgnoreExplicitTarget>("Field");

    // For a specific type with expression
    settings.IgnoreMember<IgnoreExplicitTarget>(_ => _.PropertyThatThrows);

    return Verify(target, settings);
}

[Fact]
public Task IgnoreMemberByNameFluent()
{
    var target = new IgnoreExplicitTarget
    {
        Include = "Value",
        Field = "Value",
        Property = "Value",
        PropertyByName = "Value"
    };
    return Verify(target)
        // For all types
        .IgnoreMember("PropertyByName")

        // For a specific type
        .IgnoreMember(typeof(IgnoreExplicitTarget), "Property")

        // For a specific type generic
        .IgnoreMember<IgnoreExplicitTarget>("Field")

        // For a specific type with expression
        .IgnoreMember<IgnoreExplicitTarget>(_ => _.PropertyThatThrows);
}

snippet source | anchor

Or globally:

// For all types
VerifierSettings.IgnoreMember("PropertyByName");

// For a specific type
VerifierSettings.IgnoreMember(typeof(IgnoreExplicitTarget), "Property");

// For a specific type generic
VerifierSettings.IgnoreMember<IgnoreExplicitTarget>("Field");

// For a specific type with expression
VerifierSettings.IgnoreMember<IgnoreExplicitTarget>(_ => _.PropertyThatThrows);

snippet source | anchor

Result:

{
  Include: Value,
  GetOnlyProperty: asd
}

snippet source | anchor

Scrub member by name

To scrub members of a certain type using type and name:

[Fact]
public Task ScrubMemberByName()
{
    var target = new IgnoreExplicitTarget
    {
        Include = "Value",
        Field = "Value",
        Property = "Value",
        PropertyByName = "Value"
    };
    var settings = new VerifySettings();

    // For all types
    settings.ScrubMember("PropertyByName");

    // For a specific type
    settings.ScrubMember(typeof(IgnoreExplicitTarget), "Property");

    // For a specific type generic
    settings.ScrubMember<IgnoreExplicitTarget>("Field");

    // For a specific type with expression
    settings.ScrubMember<IgnoreExplicitTarget>(_ => _.PropertyThatThrows);

    return Verify(target, settings);
}

[Fact]
public Task ScrubMemberByNameFluent()
{
    var target = new IgnoreExplicitTarget
    {
        Include = "Value",
        Field = "Value",
        Property = "Value",
        PropertyByName = "Value"
    };
    return Verify(target)
        // For all types
        .ScrubMember("PropertyByName")

        // For a specific type
        .ScrubMember(typeof(IgnoreExplicitTarget), "Property")

        // For a specific type generic
        .ScrubMember<IgnoreExplicitTarget>("Field")

        // For a specific type with expression
        .ScrubMember<IgnoreExplicitTarget>(_ => _.PropertyThatThrows);
}

snippet source | anchor

Or globally:

// For all types
VerifierSettings.ScrubMember("PropertyByName");

// For a specific type
VerifierSettings.ScrubMember(typeof(IgnoreExplicitTarget), "Property");

// For a specific type generic
VerifierSettings.ScrubMember<IgnoreExplicitTarget>("Field");

// For a specific type with expression
VerifierSettings.ScrubMember<IgnoreExplicitTarget>(_ => _.PropertyThatThrows);

snippet source | anchor

Result:

{
  Include: Value,
  Field: {Scrubbed},
  Property: {Scrubbed},
  PropertyByName: {Scrubbed},
  GetOnlyProperty: asd,
  PropertyThatThrows: {Scrubbed}
}

snippet source | anchor

Members that throw

Members that throw exceptions can be excluded from serialization based on the exception type or properties.

By default members that throw NotImplementedException or NotSupportedException are ignored.

Note that this is global for all members on all types.

Ignore by exception type:

[Fact]
public Task CustomExceptionProp()
{
    var target = new WithCustomException();
    var settings = new VerifySettings();
    settings.IgnoreMembersThatThrow<CustomException>();
    return Verify(target, settings);
}

[Fact]
public Task CustomExceptionPropFluent()
{
    var target = new WithCustomException();
    return Verify(target)
        .IgnoreMembersThatThrow<CustomException>();
}

snippet source | anchor

Or globally:

VerifierSettings.IgnoreMembersThatThrow<CustomException>();

snippet source | anchor

Result:

{}

snippet source | anchor

Ignore by exception type and expression:

[Fact]
public Task ExceptionMessageProp()
{
    var target = new WithExceptionIgnoreMessage();

    var settings = new VerifySettings();
    settings.IgnoreMembersThatThrow<Exception>(_ => _.Message == "Ignore");
    return Verify(target, settings);
}

[Fact]
public Task ExceptionMessagePropFluent()
{
    var target = new WithExceptionIgnoreMessage();

    return Verify(target)
        .IgnoreMembersThatThrow<Exception>(_ => _.Message == "Ignore");
}

snippet source | anchor

Or globally:

VerifierSettings.IgnoreMembersThatThrow<Exception>(_ => _.Message == "Ignore");

snippet source | anchor

Result:

{}

snippet source | anchor

TreatAsString

Certain types, when passed directly in to Verify, are written directly without going through json serialization.

The default mapping is:

{
    typeof(StringBuilder), (target, _) => ((StringBuilder) target).ToString()
},
{
    typeof(StringWriter), (target, _) => ((StringWriter) target).ToString()
},
{
    typeof(bool), (target, _) => ((bool) target).ToString(Culture.InvariantCulture)
},
{
    typeof(short), (target, _) => ((short) target).ToString(Culture.InvariantCulture)
},
{
    typeof(ushort), (target, _) => ((ushort) target).ToString(Culture.InvariantCulture)
},
{
    typeof(int), (target, _) => ((int) target).ToString(Culture.InvariantCulture)
},
{
    typeof(uint), (target, _) => ((uint) target).ToString(Culture.InvariantCulture)
},
{
    typeof(long), (target, _) => ((long) target).ToString(Culture.InvariantCulture)
},
{
    typeof(ulong), (target, _) => ((ulong) target).ToString(Culture.InvariantCulture)
},
{
    typeof(decimal), (target, _) => ((decimal) target).ToString(Culture.InvariantCulture)
},
{
    typeof(BigInteger), (target, _) => ((BigInteger) target).ToString(Culture.InvariantCulture)
},
#if NET5_0_OR_GREATER
{
    typeof(Half), (target, _) => ((Half) target).ToString(Culture.InvariantCulture)
},
#endif
#if NET6_0_OR_GREATER
{
    typeof(Date), (target, _) =>
    {
        var date = (Date) target;
        return date.ToString("yyyy-MM-dd", Culture.InvariantCulture);
    }
},
{
    typeof(Time), (target, _) =>
    {
        var time = (Time) target;
        return time.ToString("h:mm tt", Culture.InvariantCulture);
    }
},
#endif
{
    typeof(float), (target, _) => ((float) target).ToString(Culture.InvariantCulture)
},
{
    typeof(double), (target, _) => ((double) target).ToString(Culture.InvariantCulture)
},
{
    typeof(Guid), (target, _) => ((Guid) target).ToString()
},
{
    typeof(DateTime), (target, _) => DateFormatter.ToJsonString((DateTime) target)
},
{
    typeof(DateTimeOffset), (target, _) => DateFormatter.ToJsonString((DateTimeOffset) target)
},
{
    typeof(XmlNode), (target, _) =>
    {
        var converted = (XmlNode) target;
        var document = XDocument.Parse(converted.OuterXml);
        return new(document.ToString(), "xml");
    }
},
{
    typeof(XElement), (target, settings) =>
    {
        var converted = (XElement) target;
        return new(converted.ToString(), "xml");
    }
},

snippet source | anchor

This bypasses the Guid and DateTime scrubbing.

Extra types can be added to this mapping:

VerifierSettings.TreatAsString<ClassWithToString>(
    (target, settings) => target.Property);

snippet source | anchor

Converting a member

The value of a member can be mutated before serialization:

[ModuleInitializer]
public static void MemberConverterByExpressionInit()
{
    // using only the member
    VerifierSettings.MemberConverter<MemberTarget, string>(
        expression: _ => _.Field,
        converter: member => $"{member}_Suffix");

    // using target and member
    VerifierSettings.MemberConverter<MemberTarget, string>(
        expression: _ => _.Property,
        converter: (target, member) => $"{target}_{member}_Suffix");
}

[Fact]
public Task MemberConverterByExpression()
{
    var input = new MemberTarget
    {
        Field = "FieldValue",
        Property = "PropertyValue"
    };

    return Verify(input);
}

snippet source | anchor

SortPropertiesAlphabetically

Serialized properties can optionally be sorted alphabetically, ie ignoring the order they are defined when using reflection.

public static class ModuleInitializer
{
    [ModuleInitializer]
    public static void Init() =>
        VerifierSettings.SortPropertiesAlphabetically();
}

snippet source | anchor

Dictionary sorting

Dictionaries are sorted by key.

To disable use:

[Fact]
public Task DontSortDictionaries()
{
    var dictionary = new Dictionary<string, string>
    {
        {
            "Entry_1", "1234"
        },
        {
            "Entry_3", "1234"
        },
        {
            "Entry_2", "5678"
        }
    };

    return Verify(dictionary)
        .DontSortDictionaries();
}

snippet source | anchor

[Fact]
public Task DontSortDictionaries()
{
    var dictionary = new Dictionary<string, string>
    {
        {
            "Entry_1", "1234"
        },
        {
            "Entry_3", "1234"
        },
        {
            "Entry_2", "5678"
        }
    };

    return Verify(dictionary)
        .DontSortDictionaries();
}

snippet source | anchor

Json/JObject sorting

Json and JObject are not sorted.

To enable sorting use:

public static class ModuleInitializer
{
    [ModuleInitializer]
    public static void Init() =>
        VerifierSettings.SortJsonObjects();
}

snippet source | anchor

Ordering IEnumerable items

Items in an instance of an IEnumerable can be ordered.

This is helpful when verifying items that can have an inconsistent order, for example reading items from a database.

OrderEnumerableBy

Globally

[ModuleInitializer]
public static void OrderEnumerableByInitializer() =>
    VerifierSettings.OrderEnumerableBy<TargetForGlobal>(_ => _.Value);

snippet source | anchor

Instance

[Fact]
public Task EnumerableOrder()
{
    var settings = new VerifySettings();
    settings.OrderEnumerableBy<Target>(_ => _.Value);
    return Verify(
        new List<Target>
        {
            new("a"),
            new("c"),
            new("b")
        },
        settings);
}

snippet source | anchor

Fluent

[Fact]
public Task EnumerableOrderFluent() =>
    Verify(
            new List<Target>
            {
                new("a"),
                new("c"),
                new("b")
            })
        .OrderEnumerableBy<Target>(_ => _.Value);

snippet source | anchor

Result

The resulting file will be:

[
  {
    Value: a
  },
  {
    Value: b
  },
  {
    Value: c
  }
]

snippet source | anchor

OrderEnumerableByDescending

Globally

[ModuleInitializer]
public static void OrderEnumerableByDescendingInitializer() =>
    VerifierSettings.OrderEnumerableByDescending<TargetForGlobalDescending>(_ => _.Value);

snippet source | anchor

Instance

[Fact]
public Task OrderEnumerableByDescending()
{
    var settings = new VerifySettings();
    settings.OrderEnumerableByDescending<Target>(_ => _.Value);
    return Verify(
        new List<Target>
        {
            new("a"),
            new("c"),
            new("b")
        },
        settings);
}

snippet source | anchor

Fluent

[Fact]
public Task OrderEnumerableByDescendingFluent() =>
    Verify(
            new List<Target>
            {
                new("a"),
                new("c"),
                new("b")
            })
        .OrderEnumerableByDescending<Target>(_ => _.Value);

snippet source | anchor

Result

The resulting file will be:

[
  {
    Value: c
  },
  {
    Value: b
  },
  {
    Value: a
  }
]

snippet source | anchor

JsonAppender

A JsonAppender allows extra content (key value pairs) to be optionally appended to the output being verified. JsonAppenders can use the current context to determine what should be appended or if anything should be appended.

Register a JsonAppender:

VerifierSettings.RegisterJsonAppender(
    context =>
    {
        if (ShouldInclude(context))
        {
            return new ToAppend("theData", "theValue");
        }

        return null;
    });

snippet source | anchor

When when content is verified:

[Fact]
public Task WithJsonAppender() =>
    Verify("TheValue");

snippet source | anchor

The content from RegisterJsonAppender will be included in the output:

{
  target: TheValue,
  theData: theValue
}

snippet source | anchor

If the target is a stream or binary file:

[Fact]
public Task Stream() =>
    Verify(IoHelpers.OpenRead("sample.txt"));

snippet source | anchor

Then the appended content will be added to the .verified.txt file:

{
  target: null,
  theData: theValue
}

snippet source | anchor

See Converters for more information on *.00.verified.txt files.

Examples of extensions using JsonAppenders are Recorders in Verify.SqlServer and Recorders in Verify.EntityFramework.

See also