Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
8014b3c
Add assertion formatting helpers and localized resource strings
Evangelink Feb 22, 2026
b2127af
Improve AreEqual/AreNotEqual assertion error messages
Evangelink Feb 22, 2026
9ac4849
Improve AreSame/AreNotSame assertion error messages
Evangelink Feb 22, 2026
375f0b5
Improve IsTrue/IsFalse assertion error messages
Evangelink Feb 22, 2026
bd66356
Improve IsNull/IsNotNull assertion error messages
Evangelink Feb 22, 2026
7e7f07e
Improve IsInstanceOfType/IsNotInstanceOfType assertion error messages
Evangelink Feb 22, 2026
bebe2ef
Improve IsExactInstanceOfType/IsNotExactInstanceOfType assertion erro…
Evangelink Feb 22, 2026
d35c33b
Improve IComparable assertion error messages
Evangelink Feb 22, 2026
23758d2
Improve HasCount/IsEmpty/IsNotEmpty assertion error messages
Evangelink Feb 22, 2026
3705c74
Improve ThrowsException assertion error messages
Evangelink Feb 22, 2026
23cd627
Improve Contains/DoesNotContain/IsInRange assertion error messages
Evangelink Feb 22, 2026
dd88189
Improve StartsWith/DoesNotStartWith assertion error messages
Evangelink Feb 22, 2026
0c10f63
Improve EndsWith/DoesNotEndWith assertion error messages
Evangelink Feb 22, 2026
14a6ec3
Improve MatchesRegex/DoesNotMatchRegex assertion error messages
Evangelink Feb 22, 2026
63e8257
Minor test cleanup for Inconclusive assertion
Evangelink Feb 22, 2026
f559387
Address Copilot feedback
Evangelink Feb 22, 2026
dfe9891
Fix acceptance tests
Evangelink Feb 22, 2026
4422c82
Merge branch 'main' into dev/amauryleve/rework-assert
Evangelink Mar 11, 2026
f725f60
Fix diff caret position
Evangelink Mar 11, 2026
9c96c9b
Use X more chars instead of total length
Evangelink Mar 11, 2026
632c3c1
Use item instead of element
Evangelink Mar 11, 2026
fa263f2
Use more items
Evangelink Mar 11, 2026
2822182
Updates
Evangelink Mar 11, 2026
39b59eb
Simplify user message handling
Evangelink Mar 11, 2026
60d17a9
Update hash text
Evangelink Mar 11, 2026
44c6ca1
Improve
Evangelink Mar 11, 2026
2610e3a
Move expressions to first line
Evangelink Mar 12, 2026
a402e6d
Avoid using wildcard in expected message
Evangelink Mar 12, 2026
8eae1b2
Merge main into dev/amauryleve/rework-assert
Evangelink Mar 19, 2026
2028446
Remove redundant item count from fully-displayed collections
Evangelink Mar 19, 2026
52d43b7
Display null instead of (null) for null values in assertions
Evangelink Mar 19, 2026
6222540
Use consistent angle bracket format for type display in assertions
Evangelink Mar 19, 2026
6b1cf89
Remove angle brackets around delta values in equality assertion messages
Evangelink Mar 19, 2026
08fb47e
Fix trailing newlines in test files
Evangelink Mar 19, 2026
7f4294e
Materialize non-ICollection enumerables at assertion boundary to prev…
Evangelink Mar 19, 2026
26f5c02
WIP: In-progress assertion changes before refactor
Evangelink Mar 19, 2026
99b4922
Refactor assertion error messages with structured format
Evangelink Mar 19, 2026
edc1aa1
Add C# numeric type suffixes to FormatValue display
Evangelink Mar 19, 2026
6aad990
Improve assertion messages: drop parens, specific counts, collection …
Evangelink Mar 19, 2026
1332f35
Drop parentheses from null type display: (null) -> null
Evangelink Mar 19, 2026
c6efa3e
Improve InstanceOfType messages: embed type in sentence, combine valu…
Evangelink Mar 19, 2026
d8e4572
Add equality hint to AreSame and show hash in AreNotSame
Evangelink Mar 20, 2026
7065af5
Move user message before explanation, drop 'User message:' prefix
Evangelink Mar 20, 2026
25e7447
Replace wildcard patterns with precise expected messages in IsInRange…
Evangelink Mar 20, 2026
370e8ac
Unify assertion failure messages with structured format
Evangelink Mar 20, 2026
fa32b9a
Merge branch 'main' into dev/amauryleve/rework-assert
Evangelink Mar 20, 2026
a7c777a
Address code review findings
Evangelink Mar 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
259 changes: 110 additions & 149 deletions src/TestFramework/TestFramework/Assertions/Assert.AreEqual.cs

Large diffs are not rendered by default.

91 changes: 75 additions & 16 deletions src/TestFramework/TestFramework/Assertions/Assert.AreSame.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@ internal void ComputeAssertion(string expectedExpression, string actualExpressio
{
if (_builder is not null)
{
_builder.Insert(0, string.Format(CultureInfo.CurrentCulture, FrameworkMessages.CallerArgumentExpressionTwoParametersMessage, "expected", expectedExpression, "actual", actualExpression) + " ");
ThrowAssertAreSameFailed(_expected, _actual, _builder.ToString());
ThrowAssertAreSameFailed(_expected, _actual, _builder.ToString(), expectedExpression, actualExpression);
}
}

Expand Down Expand Up @@ -80,9 +79,13 @@ internal void ComputeAssertion(string expectedExpression, string actualExpressio
public readonly struct AssertAreNotSameInterpolatedStringHandler<TArgument>
{
private readonly StringBuilder? _builder;
private readonly TArgument? _notExpected;
private readonly TArgument? _actual;

public AssertAreNotSameInterpolatedStringHandler(int literalLength, int formattedCount, TArgument? notExpected, TArgument? actual, out bool shouldAppend)
{
_notExpected = notExpected;
_actual = actual;
shouldAppend = IsAreNotSameFailing(notExpected, actual);
if (shouldAppend)
{
Expand All @@ -94,8 +97,7 @@ internal void ComputeAssertion(string notExpectedExpression, string actualExpres
{
if (_builder is not null)
{
_builder.Insert(0, string.Format(CultureInfo.CurrentCulture, FrameworkMessages.CallerArgumentExpressionTwoParametersMessage, "notExpected", notExpectedExpression, "actual", actualExpression) + " ");
ThrowAssertAreNotSameFailed(_builder.ToString());
ThrowAssertAreNotSameFailed(_notExpected, _actual, _builder.ToString(), notExpectedExpression, actualExpression);
}
}

Expand Down Expand Up @@ -177,26 +179,63 @@ public static void AreSame<T>(T? expected, T? actual, string? message = "", [Cal
return;
}

string userMessage = BuildUserMessageForExpectedExpressionAndActualExpression(message, expectedExpression, actualExpression);
ThrowAssertAreSameFailed(expected, actual, userMessage);
ThrowAssertAreSameFailed(expected, actual, message, expectedExpression, actualExpression);
}

private static bool IsAreSameFailing<T>(T? expected, T? actual)
=> !object.ReferenceEquals(expected, actual);

[DoesNotReturn]
private static void ThrowAssertAreSameFailed<T>(T? expected, T? actual, string userMessage)
private static void ThrowAssertAreSameFailed<T>(T? expected, T? actual, string? userMessage, string expectedExpression, string actualExpression)
{
string finalMessage = userMessage;
string callSite = FormatCallSite("Assert.AreSame", (nameof(expected), expectedExpression), (nameof(actual), actualExpression));

// When both values have the same string representation, include hash codes
// to help the user understand they are different object instances.
string expectedFormatted = FormatValue(expected);
string actualFormatted = FormatValue(actual);
bool sameToString = expected is not null && actual is not null && expectedFormatted == actualFormatted;
string expectedValue = sameToString
? expectedFormatted + $" (Hash={RuntimeHelpers.GetHashCode(expected!)})"
: expectedFormatted;
string actualValue = sameToString
? actualFormatted + $" (Hash={RuntimeHelpers.GetHashCode(actual!)})"
: actualFormatted;

string message;
// If value types, add diagnostic hint before parameter details
if (expected is ValueType && actual is ValueType)
{
finalMessage = string.Format(
CultureInfo.CurrentCulture,
FrameworkMessages.AreSameGivenValues,
userMessage);
message = string.Format(CultureInfo.CurrentCulture, FrameworkMessages.AreSameGivenValues, string.Empty).TrimEnd();
}
else
{
// Check equality to give a diagnostic hint about whether the objects are equal
// even though they are different references.
string equalityHint = string.Empty;
if (expected is not null && actual is not null)
{
try
{
equalityHint = expected.Equals(actual)
? " Objects are equal."
: " Objects are not equal.";
}
catch (Exception)
{
// If Equals throws, skip the hint.
}
}

message = FrameworkMessages.AreSameFailNew + equalityHint;
}

ThrowAssertFailed("Assert.AreSame", finalMessage);
message += FormatAlignedParameters(
(nameof(expected), expectedValue),
(nameof(actual), actualValue));

message = AppendUserMessage(message, userMessage);
ThrowAssertFailed(callSite, message);
}

/// <inheritdoc cref="AreNotSame{T}(T, T, string?, string, string)" />
Expand Down Expand Up @@ -240,14 +279,34 @@ public static void AreNotSame<T>(T? notExpected, T? actual, string? message = ""
{
if (IsAreNotSameFailing(notExpected, actual))
{
ThrowAssertAreNotSameFailed(BuildUserMessageForNotExpectedExpressionAndActualExpression(message, notExpectedExpression, actualExpression));
ThrowAssertAreNotSameFailed(notExpected, actual, message, notExpectedExpression, actualExpression);
}
}

private static bool IsAreNotSameFailing<T>(T? notExpected, T? actual)
=> object.ReferenceEquals(notExpected, actual);

[DoesNotReturn]
private static void ThrowAssertAreNotSameFailed(string userMessage)
=> ThrowAssertFailed("Assert.AreNotSame", userMessage);
private static void ThrowAssertAreNotSameFailed<T>(T? notExpected, T? actual, string? userMessage, string notExpectedExpression, string actualExpression)
{
string callSite = FormatCallSite("Assert.AreNotSame", (nameof(notExpected), notExpectedExpression), (nameof(actual), actualExpression));
string message = FrameworkMessages.AreNotSameFailNew;

string notExpectedFormatted = FormatValue(notExpected);
string actualFormatted = FormatValue(actual);

// Since AreNotSame failed, both references point to the same object.
// Show the hash to confirm it's one instance.
if (notExpected is not null)
{
notExpectedFormatted += $" (Hash={RuntimeHelpers.GetHashCode(notExpected!)})";
actualFormatted += $" (Hash={RuntimeHelpers.GetHashCode(actual!)})";
}

message += FormatAlignedParameters(
("not expected", notExpectedFormatted),
(nameof(actual), actualFormatted));
message = AppendUserMessage(message, userMessage);
ThrowAssertFailed(callSite, message);
}
}
Loading
Loading