Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 6 additions & 3 deletions docs/RFCs/012-Structured-Assertion-Messages.md
Original file line number Diff line number Diff line change
Expand Up @@ -307,8 +307,9 @@ Assert.ThrowsExactly<ArgumentException>(() => Validate(input))
Assertion failed. Expected exception of exact type ArgumentException but caught InvalidOperationException.

expected type: System.ArgumentException
actual type: System.InvalidOperationException
actual exception: System.InvalidOperationException: Operation is not valid due to the current state of the object.
at MyApp.Service.Validate(String input) in Service.cs:line 42
at MyTests.ValidationTests.InvalidInput_ShouldThrow() in ValidationTests.cs:line 18

Assert.ThrowsExactly<ArgumentException>(() => Validate(input))
at MyTests.ValidationTests.InvalidInput_ShouldThrow() in ValidationTests.cs:line 18
Expand Down Expand Up @@ -679,8 +680,9 @@ Assertion failed. Expected exception of type ArgumentException (or derived) but
Assertion failed. Expected exception of type ArgumentException (or derived) but caught InvalidOperationException.

expected type: System.ArgumentException (or derived)
actual type: System.InvalidOperationException
actual exception: System.InvalidOperationException: Operation is not valid due to the current state of the object.
at MyApp.Service.Validate(String input) in Service.cs:line 42
at MyTests.ValidationTests.InvalidInput_ShouldThrow() in ValidationTests.cs:line 18
```

#### Assert.ThrowsExactly (no exception thrown)
Expand All @@ -695,8 +697,9 @@ Assertion failed. Expected exception of exact type ArgumentException but no exce
Assertion failed. Expected exception of exact type ArgumentException but caught ArgumentNullException.

expected type: System.ArgumentException
actual type: System.ArgumentNullException
actual exception: System.ArgumentNullException: Value cannot be null.
at MyApp.Service.Validate(String input) in Service.cs:line 42
at MyTests.ValidationTests.InvalidInput_ShouldThrow() in ValidationTests.cs:line 18
```

#### Assert.ThrowsAsync / Assert.ThrowsExactlyAsync
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,19 +172,28 @@ private static void ReportThrowsFailed<TException>(
Exception actualException = state.ExceptionThrown!;
Type actualType = actualException.GetType();
string actualTypeName = GetDisplayTypeName(actualType, includeNamespace: false);
string actualTypeFullName = GetDisplayTypeName(actualType, includeNamespace: true);

string summary = isStrictType
? $"Expected exception of exact type {expectedTypeName} but caught {actualTypeName}."
: $"Expected exception of type {expectedTypeName} (or derived) but caught {actualTypeName}.";

string expectedTypeLabel = isStrictType ? expectedTypeFullName : $"{expectedTypeFullName} (or derived)";

// The "actual exception:" line is already prefixed with the exception type name, so we don't emit a
// separate "actual type:" line to avoid duplicating that information.
// Render the full exception (type, message, inner-exception chain and stack trace) via ToString so the
// unexpected exception can be diagnosed without re-running under a debugger. See issue #9190.
// Exception.ToString() prefixes the output with Type.ToString(), which uses CLR notation for generic
// types (e.g. "MyException`1[System.Int32]"). Replace that leading prefix with the friendly name so it
// stays consistent with the "expected type:" line; for non-generic types the two notations are identical.
string actualExceptionText = actualException.ToString();
string clrTypeName = actualType.ToString();
if (actualExceptionText.StartsWith(clrTypeName, StringComparison.Ordinal))
{
actualExceptionText = GetDisplayTypeName(actualType, includeNamespace: true) + actualExceptionText.Substring(clrTypeName.Length);
}

EvidenceBlock evidence = EvidenceBlock.Create()
.AddLine("expected type:", expectedTypeLabel)
.AddLine("actual exception:", $"{actualTypeFullName}: {actualException.Message}");
.AddLine("actual exception:", actualExceptionText);

message = new StructuredAssertionMessage(summary)
.WithUserMessage(userMessage)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,12 @@ public void ThrowsAsync_WhenExceptionIsNotExpectedType_ShouldThrow()
Action action = t.Wait;
action.Should().Throw<AggregateException>()
.WithInnerException<AssertFailedException>()
.Which.Message.Should().Be(
.Which.Message.Should().Match(
"""
Assertion failed. Expected exception of type ArgumentException (or derived) but caught Exception.

expected type: System.ArgumentException (or derived)
actual exception: System.Exception: Exception of type 'System.Exception' was thrown.
actual exception: System.Exception: Exception of type 'System.Exception' was thrown.*

Assert.ThrowsAsync<ArgumentException>(() => throw new Exception())
""");
Expand All @@ -87,12 +87,12 @@ public void ThrowsExactlyAsync_WhenExceptionIsDerivedFromExpectedType_ShouldThro
Action action = t.Wait;
action.Should().Throw<AggregateException>()
.WithInnerException<AssertFailedException>()
.Which.Message.Should().Be(
.Which.Message.Should().Match(
"""
Assertion failed. Expected exception of exact type ArgumentException but caught ArgumentNullException.

expected type: System.ArgumentException
actual exception: System.ArgumentNullException: Value cannot be null.
actual exception: System.ArgumentNullException: Value cannot be null.*

Assert.ThrowsExactlyAsync<ArgumentException>(() => throw new ArgumentNullException())
""");
Expand Down Expand Up @@ -144,23 +144,13 @@ public void Throws_WithMessageBuilder_FailsBecauseTypeMismatch()
return "message constructed via builder.";
});
action.Should().Throw<AssertFailedException>()
.Which.Message.Should().BeOneOf(
"""
Assertion failed. Expected exception of type ArgumentNullException (or derived) but caught ArgumentOutOfRangeException.
message constructed via builder.

expected type: System.ArgumentNullException (or derived)
actual exception: System.ArgumentOutOfRangeException: Specified argument was out of the range of valid values. (Parameter 'MyParamNameHere')

Assert.Throws<ArgumentNullException>(() => throw new ArgumentOutOfRangeException("MyParamNameHere"))
""",
.Which.Message.Should().Match(
"""
Assertion failed. Expected exception of type ArgumentNullException (or derived) but caught ArgumentOutOfRangeException.
message constructed via builder.

expected type: System.ArgumentNullException (or derived)
actual exception: System.ArgumentOutOfRangeException: Specified argument was out of the range of valid values.
Parameter name: MyParamNameHere
actual exception: System.ArgumentOutOfRangeException: Specified argument was out of the range of valid values.*MyParamNameHere*

Assert.Throws<ArgumentNullException>(() => throw new ArgumentOutOfRangeException("MyParamNameHere"))
""");
Expand Down Expand Up @@ -216,23 +206,13 @@ public void ThrowsExactly_WithMessageBuilder_FailsBecauseTypeMismatch()
return "message constructed via builder.";
});
action.Should().Throw<AssertFailedException>()
.Which.Message.Should().BeOneOf(
.Which.Message.Should().Match(
"""
Assertion failed. Expected exception of exact type ArgumentNullException but caught ArgumentOutOfRangeException.
message constructed via builder.

expected type: System.ArgumentNullException
actual exception: System.ArgumentOutOfRangeException: Specified argument was out of the range of valid values. (Parameter 'MyParamNameHere')

Assert.ThrowsExactly<ArgumentNullException>(() => throw new ArgumentOutOfRangeException("MyParamNameHere"))
""",
"""
Assertion failed. Expected exception of exact type ArgumentNullException but caught ArgumentOutOfRangeException.
message constructed via builder.

expected type: System.ArgumentNullException
actual exception: System.ArgumentOutOfRangeException: Specified argument was out of the range of valid values.
Parameter name: MyParamNameHere
actual exception: System.ArgumentOutOfRangeException: Specified argument was out of the range of valid values.*MyParamNameHere*

Assert.ThrowsExactly<ArgumentNullException>(() => throw new ArgumentOutOfRangeException("MyParamNameHere"))
""");
Expand Down Expand Up @@ -288,23 +268,13 @@ public async Task ThrowsAsync_WithMessageBuilder_FailsBecauseTypeMismatch()
return "message constructed via builder.";
});
(await action.Should().ThrowAsync<AssertFailedException>())
.Which.Message.Should().BeOneOf(
.Which.Message.Should().Match(
"""
Assertion failed. Expected exception of type ArgumentNullException (or derived) but caught ArgumentOutOfRangeException.
message constructed via builder.

expected type: System.ArgumentNullException (or derived)
actual exception: System.ArgumentOutOfRangeException: Specified argument was out of the range of valid values. (Parameter 'MyParamNameHere')

Assert.ThrowsAsync<ArgumentNullException>(() => Task.FromException(new ArgumentOutOfRangeException("MyParamNameHere")))
""",
"""
Assertion failed. Expected exception of type ArgumentNullException (or derived) but caught ArgumentOutOfRangeException.
message constructed via builder.

expected type: System.ArgumentNullException (or derived)
actual exception: System.ArgumentOutOfRangeException: Specified argument was out of the range of valid values.
Parameter name: MyParamNameHere
actual exception: System.ArgumentOutOfRangeException: Specified argument was out of the range of valid values.*MyParamNameHere*

Assert.ThrowsAsync<ArgumentNullException>(() => Task.FromException(new ArgumentOutOfRangeException("MyParamNameHere")))
""");
Expand Down Expand Up @@ -360,23 +330,13 @@ public async Task ThrowsExactlyAsync_WithMessageBuilder_FailsBecauseTypeMismatch
return "message constructed via builder.";
});
(await action.Should().ThrowAsync<AssertFailedException>())
.Which.Message.Should().BeOneOf(
"""
Assertion failed. Expected exception of exact type ArgumentNullException but caught ArgumentOutOfRangeException.
message constructed via builder.

expected type: System.ArgumentNullException
actual exception: System.ArgumentOutOfRangeException: Specified argument was out of the range of valid values. (Parameter 'MyParamNameHere')

Assert.ThrowsExactlyAsync<ArgumentNullException>(() => Task.FromException(new ArgumentOutOfRangeException("MyParamNameHere")))
""",
.Which.Message.Should().Match(
"""
Assertion failed. Expected exception of exact type ArgumentNullException but caught ArgumentOutOfRangeException.
message constructed via builder.

expected type: System.ArgumentNullException
actual exception: System.ArgumentOutOfRangeException: Specified argument was out of the range of valid values.
Parameter name: MyParamNameHere
actual exception: System.ArgumentOutOfRangeException: Specified argument was out of the range of valid values.*MyParamNameHere*

Assert.ThrowsExactlyAsync<ArgumentNullException>(() => Task.FromException(new ArgumentOutOfRangeException("MyParamNameHere")))
""");
Expand Down Expand Up @@ -421,13 +381,13 @@ public void Throws_WhenExceptionMessageContainsNewline_ContinuationLinesAreInden

// "actual exception:" is the longest label (17 chars) + 1 space = 18 chars indent for the continuation line.
action.Should().Throw<AssertFailedException>()
.Which.Message.Should().Be(
.Which.Message.Should().Match(
"""
Assertion failed. Expected exception of type ArgumentException (or derived) but caught InvalidOperationException.

expected type: System.ArgumentException (or derived)
actual exception: System.InvalidOperationException: line1
line2
line2*

Assert.Throws<ArgumentException>(() => throw new InvalidOperationException("line1\nline2"))
""");
Expand All @@ -445,12 +405,12 @@ static void Action() => Assert.Throws<ArgumentException>(() =>
Action action = Action;

action.Should().Throw<AssertFailedException>()
.Which.Message.Should().Be(
.Which.Message.Should().Match(
"""
Assertion failed. Expected exception of type ArgumentException (or derived) but caught InvalidOperationException.

expected type: System.ArgumentException (or derived)
actual exception: System.InvalidOperationException: oops
actual exception: System.InvalidOperationException: oops*

Assert.Throws<ArgumentException>(<action>)
""");
Expand All @@ -464,12 +424,12 @@ public void Throws_WhenExpectedTypeIsGeneric_RendersFriendlyTypeName()
Action action = Action;

action.Should().Throw<AssertFailedException>()
.Which.Message.Should().Be(
.Which.Message.Should().Match(
"""
Assertion failed. Expected exception of type ThrowsTestGenericException<Int32> (or derived) but caught InvalidOperationException.

expected type: Microsoft.VisualStudio.TestPlatform.TestFramework.UnitTests.ThrowsTestGenericException<System.Int32> (or derived)
actual exception: System.InvalidOperationException: oops
actual exception: System.InvalidOperationException: oops*

Assert.Throws<ThrowsTestGenericException<Int32>>(() => throw new InvalidOperationException("oops"))
""");
Expand Down
Loading