Certain types, when passed directly to Verify(), are written directly without going through json serialization.
The API for controlling this behavior is TreatAsString()
Example of passing directly to Verify():
[Fact]
public Task Example() =>
Verify(new DateOnly(2020, 10, 4));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 NET6_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.Convert((DateTime) target)
},
{
typeof(DateTimeOffset), (target, _) => DateFormatter.Convert((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 approach bypasses the Guid and DateTime scrubbing.
How DateTimes are converted to a string:
namespace VerifyTests;
public static partial class DateFormatter
{
public static string Convert(DateTime value)
{
var result = GetJsonDatePart(value);
if (value.Kind != DateTimeKind.Unspecified)
{
result += $" {value.Kind}";
}
return result;
}
static string GetJsonDatePart(DateTime value)
{
if (value.TimeOfDay == TimeSpan.Zero)
{
return value.ToString("yyyy-MM-dd", Culture.InvariantCulture);
}
if (value is {Second: 0, Millisecond: 0})
{
return value.ToString("yyyy-MM-dd HH:mm", Culture.InvariantCulture);
}
if (value.Millisecond == 0)
{
return value.ToString("yyyy-MM-dd HH:mm:ss", Culture.InvariantCulture);
}
return value.ToString("yyyy-MM-dd HH:mm:ss.FFFFFFF", Culture.InvariantCulture);
}
public static string ToParameterString(DateTime value)
{
var result = GetParameterDatePart(value);
if (value.Kind != DateTimeKind.Unspecified)
{
result += value.Kind;
}
return result;
}
static string GetParameterDatePart(DateTime value)
{
if (value.TimeOfDay == TimeSpan.Zero)
{
return value.ToString("yyyy-MM-dd", Culture.InvariantCulture);
}
if (value is {Second: 0, Millisecond: 0})
{
return value.ToString("yyyy-MM-ddTHH-mm", Culture.InvariantCulture);
}
if (value.Millisecond == 0)
{
return value.ToString("yyyy-MM-ddTHH-mm-ss", Culture.InvariantCulture);
}
return value.ToString("yyyy-MM-ddTHH-mm-ss.FFFFFFF", Culture.InvariantCulture);
}
}How DateTimeOffset are converted to a string:
namespace VerifyTests;
public static partial class DateFormatter
{
public static string Convert(DateTimeOffset value)
{
var result = GetJsonDatePart(value);
result += $" {GetDateOffset(value)}";
return result;
}
static string GetJsonDatePart(DateTimeOffset value)
{
if (value.TimeOfDay == TimeSpan.Zero)
{
return value.ToString("yyyy-MM-dd", Culture.InvariantCulture);
}
if (value is {Second: 0, Millisecond: 0})
{
return value.ToString("yyyy-MM-dd HH:mm", Culture.InvariantCulture);
}
if (value.Millisecond == 0)
{
return value.ToString("yyyy-MM-dd HH:mm:ss", Culture.InvariantCulture);
}
return value.ToString("yyyy-MM-dd HH:mm:ss.FFFFFFF", Culture.InvariantCulture);
}
public static string ToParameterString(DateTimeOffset value)
{
var result = GetParameterDatePart(value);
result += GetDateOffset(value);
return result;
}
static string GetParameterDatePart(DateTimeOffset value)
{
if (value.TimeOfDay == TimeSpan.Zero)
{
return value.ToString("yyyy-MM-dd", Culture.InvariantCulture);
}
if (value is {Second: 0, Millisecond: 0})
{
return value.ToString("yyyy-MM-ddTHH-mm", Culture.InvariantCulture);
}
if (value.Millisecond == 0)
{
return value.ToString("yyyy-MM-ddTHH-mm-ss", Culture.InvariantCulture);
}
return value.ToString("yyyy-MM-ddTHH-mm-ss.FFFFFFF", Culture.InvariantCulture);
}
static string GetDateOffset(DateTimeOffset value)
{
var offset = value.Offset;
if (offset > TimeSpan.Zero)
{
if (offset.Minutes == 0)
{
return $"+{offset.TotalHours:0}";
}
return $"+{offset.Hours:0}-{offset.Minutes:00}";
}
if (offset < TimeSpan.Zero)
{
if (offset.Minutes == 0)
{
return $"{offset.Hours:0}";
}
return $"{offset.Hours:0}{offset.Minutes:00}";
}
return "+0";
}
}The default TreatAsString behavior can be overridden:
VerifierSettings.TreatAsString<DateTime>(
(target, settings) => target.ToString("D"));Extra types can be added to this mapping:
VerifierSettings.TreatAsString<ClassWithToString>(
(target, settings) => target.Property);Since this approach bypasses json serialization, any json serialization settings are redundant. For example DontScrubDateTimes, UseStrictJson, and DontScrubGuids.
Note that any json serialization settings will still apply to anything amended to the target via Recording or JsonAppenders