Type to string mapping
March 15, 2026 ยท View on GitHub
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));
Default type mapping
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");
}
},
Scrubbing is bypassed
This approach bypasses the Guid and DateTime scrubbing.
DateTime formatting
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);
}
}
DateTimeOffset formatting
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";
}
}
Override TreatAsString defaults
The default TreatAsString behavior can be overridden:
VerifierSettings.TreatAsString<DateTime>(
(target, settings) => target.ToString("D"));
Extra Types
Extra types can be added to this mapping:
VerifierSettings.TreatAsString<ClassWithToString>(
(target, settings) => target.Property);
Redundant json serialization settings
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