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.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 approach bypasses the Guid and DateTime scrubbing.
How DateTimes are converted to a string:
static partial class DateFormatter
{
public static string ToJsonString(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:
static partial class DateFormatter
{
public static string ToJsonString(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