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.
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.
To use strict json call VerifierSettings.UseStrictJson
:
[ModuleInitializer]
public static void Init() =>
VerifierSettings.UseStrictJson();
Then this result in
- The default
.received.
and.verified.
extensions for serialized verification to be.json
. JsonTextWriter.QuoteChar
to be"
.JsonTextWriter.QuoteName
to betrue
.
Then when an object is verified:
var target = new TheTarget
{
Value = "Foo"
};
await Verify(target);
The resulting file will be:
{
"Value": "Foo"
}
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();
}
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);
}
}
Verify uses Argon for serialization.
Argon is a JSON framework for .NET. It is a hard fork of Newtonsoft.Json.
The default JsonSerializerSettings
are:
var settings = new JsonSerializerSettings
{
Formatting = Formatting.Indented,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
DefaultValueHandling = DefaultValueHandling.Ignore
};
VerifierSettings
.AddExtraSettings(_ =>
_.TypeNameHandling = TypeNameHandling.All);
[Fact]
public Task AddExtraSettings()
{
var settings = new VerifySettings();
settings
.AddExtraSettings(
_ => _.Error = (currentObject, originalObject, location, exception, handled) =>
Console.WriteLine(location.Member));
return Verify("Value", settings);
}
[Fact]
public Task AddExtraSettingsFluent() =>
Verify("Value")
.AddExtraSettings(
_ => _.Error = (currentObject, originalObject, location, exception, handled) =>
Console.WriteLine(location.Member));
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.
By default empty collections are ignored during verification.
To disable this behavior globally use:
VerifierSettings.DontIgnoreEmptyCollections();
By default guids are sanitized during verification. This is done by finding each guid and taking a counter based that that specific guid. That counter is then used replace the guid values. This allows for repeatable tests when guid values are changing.
var guid = Guid.NewGuid();
var target = new GuidTarget
{
Guid = guid,
GuidNullable = guid,
GuidString = guid.ToString(),
OtherGuid = Guid.NewGuid()
};
await Verify(target);
Results in the following:
{
Guid: Guid_1,
GuidNullable: Guid_1,
GuidString: Guid_1,
OtherGuid: Guid_2
}
Strings containing inline Guids can also be scrubbed. To enable this behavior, use:
VerifierSettings.ScrubInlineGuids();
To disable this behavior use:
var settings = new VerifySettings();
settings.DontScrubGuids();
await Verify(target, settings);
Or with the fluent api:
await Verify(target)
.DontScrubGuids();
To disable this behavior globally use:
VerifierSettings.DontScrubGuids();
Specific Guids can be named. When any of those Guids are found, it will be replaced with the supplied name.
[Fact]
public Task InstanceNamedGuid() =>
Verify(
new
{
value = new Guid("c8eeaf99-d5c4-4341-8543-4597c3fd40d9")
})
.AddNamedGuid(new("c8eeaf99-d5c4-4341-8543-4597c3fd40d9"), "instanceNamed");
[ModuleInitializer]
public static void Init() =>
VerifierSettings.AddNamedGuid(new("c8eeaf99-d5c4-4341-8543-4597c3fd40c9"), "guidName");
By default dates and times (DateTime
, DateTimeOffset
, DateOnly
, and TimeOnly
) are sanitized during verification. This is done by finding each date and taking a counter based that that specific date. That counter is then used replace the date values. This allows for repeatable tests when date values are changing.
var dateTime = DateTime.Now;
var dateTimeOffset = DateTimeOffset.Now;
var target = new DateTimeTarget
{
DateTime = dateTime,
Date = new(dateTime.Year, dateTime.Month, dateTime.Day),
DateNullable = new(dateTime.Year, dateTime.Month, dateTime.Day),
DateString = new Date(dateTime.Year, dateTime.Month, dateTime.Day).ToString(),
DateTimeNullable = dateTime,
DateTimeString = dateTime.ToString("F"),
DateTimeOffset = dateTimeOffset,
DateTimeOffsetNullable = dateTimeOffset,
DateTimeOffsetString = dateTimeOffset.ToString("F")
};
await Verify(target);
Results in the following:
{
DateTime: DateTime_1,
DateTimeNullable: DateTime_1,
Date: Date_1,
DateNullable: Date_1,
DateTimeOffset: DateTimeOffset_1,
DateTimeOffsetNullable: DateTimeOffset_1,
DateTimeString: DateTimeOffset_2,
DateTimeOffsetString: DateTimeOffset_2,
DateString: Date_1
}
To disable this behavior use:
var target = new
{
Date = new DateTime(2020, 10, 10, 0, 0, 0, DateTimeKind.Utc)
};
var settings = new VerifySettings();
settings.DontScrubDateTimes();
return Verify(target, settings);
Or using the fluent api use:
var target = new
{
Date = new DateTime(2020, 10, 10, 0, 0, 0, DateTimeKind.Utc)
};
return Verify(target)
.DontScrubDateTimes();
Or globally use:
VerifierSettings.DontScrubDateTimes();
AddExtraDatetimeFormat
allows specifiying custom date formats to be scrubbed.
[ModuleInitializer]
public static void UseAddExtraDatetimeFormat() =>
VerifierSettings.AddExtraDatetimeFormat("yyyy-MM-dd");
[Fact]
public Task WithExtraDatetimeFormat() =>
Verify(
new
{
date = "2022-11-08"
});
Specific date or times can be named. When any of those values are found, they will be matched with the corresponding name.
await Verify(target)
.AddNamedDate(new(2020, 10, 11), "instanceNamedDate")
.AddNamedTime(new(1, 2), "instanceTime")
.AddNamedDateTime(new(2030, 1, 2), "instanceNamedDateTime")
.AddNamedDateTimeOffset(new DateTime(2030, 1, 2), "instanceNamedTimeOffset");
[ModuleInitializer]
public static void AddNamedDatesAndTimes()
{
VerifierSettings.AddNamedDateTime(new(2030, 1, 1), "namedDateTime");
VerifierSettings.AddNamedTime(new(1, 1), "namedTime");
VerifierSettings.AddNamedDate(new(2030, 1, 1), "namedDate");
VerifierSettings.AddNamedDateTimeOffset(new(new(2030, 1, 1)), "namedDateTimeOffset");
}
DateTime
, DateTimeOffset
, Guid
, bool
, and empty collection behavior can also be controlled at the verification level:
var settings = new VerifySettings();
settings.DontIgnoreEmptyCollections();
settings.DontScrubGuids();
settings.DontScrubDateTimes();
await Verify(target, settings);
await Verify(target)
.DontIgnoreEmptyCollections()
.DontScrubGuids()
.DontScrubDateTimes();
Extra Json.NET settings can be made:
VerifierSettings.AddExtraSettings(
_ => _.TypeNameHandling = TypeNameHandling.All);
var settings = new VerifySettings();
settings.AddExtraSettings(
_ => _.TypeNameHandling = TypeNameHandling.All);
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");
}
VerifierSettings.AddExtraSettings(
_ => _.Converters.Add(new CompanyConverter()));
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 currentJsonSerializer
.Serialize(object value)
is a convenience method that callsJsonSerializer.Serialize
passing in the writer instance and thevalue
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.
[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);
}
Result:
{
$type: VerifyObjectSamples.Person,
GivenNames: John,
FamilyName: Smith
}
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>();
}
Or globally:
VerifierSettings.IgnoreMembersWithType<ToIgnore>();
Result:
{
ToInclude: {
Property: Value
},
ToIncludeNullable: {
Property: Value
},
ToIncludeStruct: {
Property: Value
},
ToIncludeStructNullable: {
Property: Value
}
}
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>();
}
Or globally:
VerifierSettings.ScrubMembersWithType<ToIgnore>();
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
}
}
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");
}
Or globally:
VerifierSettings.IgnoreInstance<Instance>(_ => _.Property == "Ignore");
Result:
{
ToInclude: {
Property: Include
}
}
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");
}
Or globally:
VerifierSettings.ScrubInstance<Instance>(_ => _.Property == "Ignore");
Result:
{
ToIgnore: {Scrubbed},
ToInclude: {
Property: Include
}
}
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);
}
Result:
{
OtherProperty: value2
}
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();
}
Or globally:
VerifierSettings.IncludeObsoletes();
Result:
{
ObsoleteProperty: value1,
OtherProperty: value2
}
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);
}
Or globally
VerifierSettings.IgnoreMembers<IgnoreExplicitTarget>(
_ => _.Property,
_ => _.PropertyWithPropertyName,
_ => _.Field,
_ => _.GetOnlyProperty,
_ => _.PropertyThatThrows);
Result:
{
Include: Value
}
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);
}
Or globally
VerifierSettings.ScrubMembers<IgnoreExplicitTarget>(
_ => _.Property,
_ => _.PropertyWithPropertyName,
_ => _.Field,
_ => _.GetOnlyProperty,
_ => _.PropertyThatThrows);
Result:
{
Include: Value,
Field: {Scrubbed},
Property: {Scrubbed},
_Custom: {Scrubbed},
GetOnlyProperty: {Scrubbed},
PropertyThatThrows: {Scrubbed}
}
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);
}
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);
Result:
{
Include: Value,
GetOnlyProperty: asd
}
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);
}
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);
Result:
{
Include: Value,
Field: {Scrubbed},
Property: {Scrubbed},
PropertyByName: {Scrubbed},
GetOnlyProperty: asd,
PropertyThatThrows: {Scrubbed}
}
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>();
}
Or globally:
VerifierSettings.IgnoreMembersThatThrow<CustomException>();
Result:
{}
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");
}
Or globally:
VerifierSettings.IgnoreMembersThatThrow<Exception>(_ => _.Message == "Ignore");
Result:
{}
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(CultureInfo.InvariantCulture)
},
{
typeof(short), (target, _) => ((short) target).ToString(CultureInfo.InvariantCulture)
},
{
typeof(ushort), (target, _) => ((ushort) target).ToString(CultureInfo.InvariantCulture)
},
{
typeof(int), (target, _) => ((int) target).ToString(CultureInfo.InvariantCulture)
},
{
typeof(uint), (target, _) => ((uint) target).ToString(CultureInfo.InvariantCulture)
},
{
typeof(long), (target, _) => ((long) target).ToString(CultureInfo.InvariantCulture)
},
{
typeof(ulong), (target, _) => ((ulong) target).ToString(CultureInfo.InvariantCulture)
},
{
typeof(decimal), (target, _) => ((decimal) target).ToString(CultureInfo.InvariantCulture)
},
{
typeof(BigInteger), (target, _) => ((BigInteger) target).ToString(CultureInfo.InvariantCulture)
},
#if NET5_0_OR_GREATER
{
typeof(Half), (target, _) => ((Half) target).ToString(CultureInfo.InvariantCulture)
},
#endif
#if NET6_0_OR_GREATER
{
typeof(Date), (target, _) =>
{
var date = (Date) target;
return date.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
}
},
{
typeof(Time), (target, _) =>
{
var time = (Time) target;
return time.ToString("h:mm tt", CultureInfo.InvariantCulture);
}
},
#endif
{
typeof(float), (target, _) => ((float) target).ToString(CultureInfo.InvariantCulture)
},
{
typeof(double), (target, _) => ((double) target).ToString(CultureInfo.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");
}
},
This bypasses the Guid and DateTime scrubbing mentioned above.
Extra types can be added to this mapping:
VerifierSettings.TreatAsString<ClassWithToString>(
(target, settings) => target.Property);
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);
}
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();
}
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();
}
Json and JObject are not sorted.
To enable sorting use:
public static class ModuleInitializer
{
[ModuleInitializer]
public static void Init() =>
VerifierSettings.SortJsonObjects();
}
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;
});
When when content is verified:
[Fact]
public Task WithJsonAppender() =>
Verify("TheValue");
The content from RegisterJsonAppender will be included in the output:
{
target: TheValue,
theData: theValue
}
If the target is a stream or binary file:
[Fact]
public Task Stream() =>
Verify(IoHelpers.OpenRead("sample.txt"));
Then the appended content will be added to the .verified.txt
file:
{
target: null,
theData: theValue
}
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.