diff --git a/src/TestFramework/TestFramework/Assertions/Assert.AreSame.cs b/src/TestFramework/TestFramework/Assertions/Assert.AreSame.cs index 9b92297c9d..dced467094 100644 --- a/src/TestFramework/TestFramework/Assertions/Assert.AreSame.cs +++ b/src/TestFramework/TestFramework/Assertions/Assert.AreSame.cs @@ -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) + " "); - ReportAssertAreSameFailed(_expected, _actual, _builder.ToString()); + ReportAssertAreSameFailed(_expected, _actual, _builder.ToString(), expectedExpression, actualExpression); } } @@ -98,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) + " "); - ReportAssertAreNotSameFailed(_notExpected, _actual, _builder.ToString()); + ReportAssertAreNotSameFailed(_notExpected, _actual, _builder.ToString(), notExpectedExpression, actualExpression); } } @@ -181,34 +179,37 @@ public static void AreSame(T? expected, T? actual, string? message = "", [Cal return; } - string userMessage = BuildUserMessageForExpectedExpressionAndActualExpression(message, expectedExpression, actualExpression); - ReportAssertAreSameFailed(expected, actual, userMessage); + ReportAssertAreSameFailed(expected, actual, message, expectedExpression, actualExpression); } private static bool IsAreSameFailing(T? expected, T? actual) => !object.ReferenceEquals(expected, actual); [DoesNotReturn] - private static void ReportAssertAreSameFailed(T? expected, T? actual, string userMessage) + private static void ReportAssertAreSameFailed(T? expected, T? actual, string? userMessage, string expectedExpression, string actualExpression) { - string finalMessage = expected is null - ? string.Format( - CultureInfo.CurrentCulture, - FrameworkMessages.AreSameExpectedIsNull, - userMessage) - : actual is null - ? string.Format( - CultureInfo.CurrentCulture, - FrameworkMessages.AreSameActualIsNull, - userMessage) - : expected is ValueType && actual is ValueType - ? string.Format( - CultureInfo.CurrentCulture, - FrameworkMessages.AreSameGivenValues, - userMessage) - : userMessage; - - ReportAssertFailed("Assert.AreSame", finalMessage); + StructuredAssertionMessage msg = new("Expected both values to refer to the same object."); + + if (expected is ValueType && actual is ValueType) + { + msg.WithAdditionalSummaryLine("Do not pass value types to AreSame \u2014 value types are boxed on each call, so references will never be the same."); + } + + msg.WithUserMessage(userMessage); + + if (expected is not ValueType || actual is not ValueType) + { + string expectedText = expected is null ? "null" : $"{expected.GetType()} (hash: 0x{RuntimeHelpers.GetHashCode(expected):X})"; + string actualText = actual is null ? "null" : $"{actual.GetType()} (hash: 0x{RuntimeHelpers.GetHashCode(actual):X})"; + EvidenceBlock evidence = EvidenceBlock.Create() + .AddLine("expected:", expectedText) + .AddLine("actual:", actualText); + msg.WithEvidence(evidence).WithExpectedAndActual(expectedText, actualText); + } + + msg.WithCallSiteExpression(FormatCallSiteExpression("Assert.AreSame", expectedExpression, actualExpression, "", "")); + + ReportAssertFailed(msg); } /// @@ -252,7 +253,7 @@ public static void AreNotSame(T? notExpected, T? actual, string? message = "" { if (IsAreNotSameFailing(notExpected, actual)) { - ReportAssertAreNotSameFailed(notExpected, actual, BuildUserMessageForNotExpectedExpressionAndActualExpression(message, notExpectedExpression, actualExpression)); + ReportAssertAreNotSameFailed(notExpected, actual, message, notExpectedExpression, actualExpression); } } @@ -260,15 +261,19 @@ private static bool IsAreNotSameFailing(T? notExpected, T? actual) => object.ReferenceEquals(notExpected, actual); [DoesNotReturn] - private static void ReportAssertAreNotSameFailed(T? notExpected, T? actual, string userMessage) + private static void ReportAssertAreNotSameFailed(T? notExpected, T? actual, string? userMessage, string notExpectedExpression, string actualExpression) { - string finalMessage = notExpected is null && actual is null - ? string.Format( - CultureInfo.CurrentCulture, - FrameworkMessages.AreNotSameBothNull, - userMessage) - : userMessage; - - ReportAssertFailed("Assert.AreNotSame", finalMessage); + StructuredAssertionMessage msg = new("Expected values to refer to different objects."); + + msg.WithAdditionalSummaryLine( + notExpected is null && actual is null + ? "Both values are null." + : "Both values refer to the same object."); + + msg.WithUserMessage(userMessage); + + msg.WithCallSiteExpression(FormatCallSiteExpression("Assert.AreNotSame", notExpectedExpression, actualExpression, "", "")); + + ReportAssertFailed(msg); } } diff --git a/src/TestFramework/TestFramework/Assertions/Assert.IsExactInstanceOfType.cs b/src/TestFramework/TestFramework/Assertions/Assert.IsExactInstanceOfType.cs index 3404a7ac57..7961cda03e 100644 --- a/src/TestFramework/TestFramework/Assertions/Assert.IsExactInstanceOfType.cs +++ b/src/TestFramework/TestFramework/Assertions/Assert.IsExactInstanceOfType.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.ComponentModel; @@ -38,8 +38,7 @@ internal void ComputeAssertion(string valueExpression) { if (_builder is not null) { - _builder.Insert(0, string.Format(CultureInfo.CurrentCulture, FrameworkMessages.CallerArgumentExpressionSingleParameterMessage, "value", valueExpression) + " "); - ReportAssertIsExactInstanceOfTypeFailed(_value, _expectedType, _builder.ToString()); + ReportAssertIsExactInstanceOfTypeFailed(_value, _expectedType, _builder.ToString(), valueExpression); } } @@ -98,8 +97,7 @@ internal void ComputeAssertion(string valueExpression) { if (_builder is not null) { - _builder.Insert(0, string.Format(CultureInfo.CurrentCulture, FrameworkMessages.CallerArgumentExpressionSingleParameterMessage, "value", valueExpression) + " "); - ReportAssertIsExactInstanceOfTypeFailed(_value, typeof(TArg), _builder.ToString()); + ReportAssertIsExactInstanceOfTypeFailed(_value, typeof(TArg), _builder.ToString(), valueExpression); } } @@ -160,8 +158,7 @@ internal void ComputeAssertion(string valueExpression) { if (_builder is not null) { - _builder.Insert(0, string.Format(CultureInfo.CurrentCulture, FrameworkMessages.CallerArgumentExpressionSingleParameterMessage, "value", valueExpression) + " "); - ReportAssertIsNotExactInstanceOfTypeFailed(_value, _wrongType, _builder.ToString()); + ReportAssertIsNotExactInstanceOfTypeFailed(_value, _wrongType, _builder.ToString(), valueExpression); } } @@ -220,8 +217,7 @@ internal void ComputeAssertion(string valueExpression) { if (_builder is not null) { - _builder.Insert(0, string.Format(CultureInfo.CurrentCulture, FrameworkMessages.CallerArgumentExpressionSingleParameterMessage, "value", valueExpression) + " "); - ReportAssertIsNotExactInstanceOfTypeFailed(_value, typeof(TArg), _builder.ToString()); + ReportAssertIsNotExactInstanceOfTypeFailed(_value, typeof(TArg), _builder.ToString(), valueExpression); } } @@ -291,7 +287,7 @@ public static void IsExactInstanceOfType([NotNull] object? value, [NotNull] Type { if (IsExactInstanceOfTypeFailing(value, expectedType)) { - ReportAssertIsExactInstanceOfTypeFailed(value, expectedType, BuildUserMessageForValueExpression(message, valueExpression)); + ReportAssertIsExactInstanceOfTypeFailed(value, expectedType, message, valueExpression); } } @@ -329,18 +325,25 @@ private static bool IsExactInstanceOfTypeFailing([NotNullWhen(false)] object? va => expectedType is null || value is null || value.GetType() != expectedType; [DoesNotReturn] - private static void ReportAssertIsExactInstanceOfTypeFailed(object? value, Type? expectedType, string userMessage) + private static void ReportAssertIsExactInstanceOfTypeFailed(object? value, Type? expectedType, string? userMessage, string valueExpression) { - string finalMessage = expectedType is not null - ? string.Format( - CultureInfo.CurrentCulture, - FrameworkMessages.IsExactInstanceOfFailMsg, - userMessage, - expectedType.ToString(), - value?.GetType().ToString() ?? "null") - : userMessage; - - ReportAssertFailed("Assert.IsExactInstanceOfType", finalMessage); + StructuredAssertionMessage msg = expectedType is null + ? new("Cannot check type because the expected type argument is null.") + : new($"Expected value to be exactly of type {expectedType.Name}."); + msg.WithUserMessage(userMessage); + + if (expectedType is not null) + { + string actualTypeText = value?.GetType().ToString() ?? "null"; + EvidenceBlock evidence = EvidenceBlock.Create() + .AddLine("expected type:", expectedType.ToString()) + .AddLine(value is null ? "actual:" : "actual type:", actualTypeText); + msg.WithEvidence(evidence) + .WithExpectedAndActual(expectedType.ToString(), actualTypeText); + } + + msg.WithCallSiteExpression(FormatCallSiteExpression("Assert.IsExactInstanceOfType", valueExpression, "")); + ReportAssertFailed(msg); } /// @@ -371,7 +374,7 @@ public static void IsNotExactInstanceOfType(object? value, [NotNull] Type? wrong { if (IsNotExactInstanceOfTypeFailing(value, wrongType)) { - ReportAssertIsNotExactInstanceOfTypeFailed(value, wrongType, BuildUserMessageForValueExpression(message, valueExpression)); + ReportAssertIsNotExactInstanceOfTypeFailed(value, wrongType, message, valueExpression); } } @@ -403,19 +406,24 @@ private static bool IsNotExactInstanceOfTypeFailing(object? value, [NotNullWhen( (value is not null && value.GetType() == wrongType); [DoesNotReturn] - private static void ReportAssertIsNotExactInstanceOfTypeFailed(object? value, Type? wrongType, string userMessage) + private static void ReportAssertIsNotExactInstanceOfTypeFailed(object? value, Type? wrongType, string? userMessage, string valueExpression) { - string finalMessage = userMessage; + StructuredAssertionMessage msg = wrongType is null + ? new("Cannot check type because the not-expected type argument is null.") + : new($"Expected value to not be exactly of type {wrongType.Name}."); + msg.WithUserMessage(userMessage); + if (wrongType is not null) { - finalMessage = string.Format( - CultureInfo.CurrentCulture, - FrameworkMessages.IsNotExactInstanceOfFailMsg, - userMessage, - wrongType.ToString(), - value!.GetType().ToString()); + string actualTypeText = value?.GetType().ToString() ?? "null"; + EvidenceBlock evidence = EvidenceBlock.Create() + .AddLine("not expected type:", wrongType.ToString()) + .AddLine("actual type:", actualTypeText); + msg.WithEvidence(evidence) + .WithExpectedAndActual(wrongType.ToString(), actualTypeText); } - ReportAssertFailed("Assert.IsNotExactInstanceOfType", finalMessage); + msg.WithCallSiteExpression(FormatCallSiteExpression("Assert.IsNotExactInstanceOfType", valueExpression, "")); + ReportAssertFailed(msg); } } diff --git a/src/TestFramework/TestFramework/Assertions/Assert.IsInstanceOfType.cs b/src/TestFramework/TestFramework/Assertions/Assert.IsInstanceOfType.cs index 27f06595b0..a9a9231217 100644 --- a/src/TestFramework/TestFramework/Assertions/Assert.IsInstanceOfType.cs +++ b/src/TestFramework/TestFramework/Assertions/Assert.IsInstanceOfType.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.ComponentModel; @@ -38,8 +38,7 @@ internal void ComputeAssertion(string valueExpression) { if (_builder is not null) { - _builder.Insert(0, string.Format(CultureInfo.CurrentCulture, FrameworkMessages.CallerArgumentExpressionSingleParameterMessage, "value", valueExpression) + " "); - ReportAssertIsInstanceOfTypeFailed(_value, _expectedType, _builder.ToString()); + ReportAssertIsInstanceOfTypeFailed(_value, _expectedType, _builder.ToString(), valueExpression); } } @@ -98,8 +97,7 @@ internal void ComputeAssertion(string valueExpression) { if (_builder is not null) { - _builder.Insert(0, string.Format(CultureInfo.CurrentCulture, FrameworkMessages.CallerArgumentExpressionSingleParameterMessage, "value", valueExpression) + " "); - ReportAssertIsInstanceOfTypeFailed(_value, typeof(TArg), _builder.ToString()); + ReportAssertIsInstanceOfTypeFailed(_value, typeof(TArg), _builder.ToString(), valueExpression); } } @@ -160,8 +158,7 @@ internal void ComputeAssertion(string valueExpression) { if (_builder is not null) { - _builder.Insert(0, string.Format(CultureInfo.CurrentCulture, FrameworkMessages.CallerArgumentExpressionSingleParameterMessage, "value", valueExpression) + " "); - ReportAssertIsNotInstanceOfTypeFailed(_value, _wrongType, _builder.ToString()); + ReportAssertIsNotInstanceOfTypeFailed(_value, _wrongType, _builder.ToString(), valueExpression); } } @@ -220,8 +217,7 @@ internal void ComputeAssertion(string valueExpression) { if (_builder is not null) { - _builder.Insert(0, string.Format(CultureInfo.CurrentCulture, FrameworkMessages.CallerArgumentExpressionSingleParameterMessage, "value", valueExpression) + " "); - ReportAssertIsNotInstanceOfTypeFailed(_value, typeof(TArg), _builder.ToString()); + ReportAssertIsNotInstanceOfTypeFailed(_value, typeof(TArg), _builder.ToString(), valueExpression); } } @@ -292,7 +288,7 @@ public static void IsInstanceOfType([NotNull] object? value, [NotNull] Type? exp { if (IsInstanceOfTypeFailing(value, expectedType)) { - ReportAssertIsInstanceOfTypeFailed(value, expectedType, BuildUserMessageForValueExpression(message, valueExpression)); + ReportAssertIsInstanceOfTypeFailed(value, expectedType, message, valueExpression); } } @@ -331,18 +327,25 @@ private static bool IsInstanceOfTypeFailing([NotNullWhen(false)] object? value, => expectedType == null || value == null || !expectedType.IsInstanceOfType(value); [DoesNotReturn] - private static void ReportAssertIsInstanceOfTypeFailed(object? value, Type? expectedType, string userMessage) + private static void ReportAssertIsInstanceOfTypeFailed(object? value, Type? expectedType, string? userMessage, string valueExpression) { - string finalMessage = expectedType is not null - ? string.Format( - CultureInfo.CurrentCulture, - FrameworkMessages.IsInstanceOfFailMsg, - userMessage, - expectedType.ToString(), - value?.GetType().ToString() ?? "null") - : userMessage; - - ReportAssertFailed("Assert.IsInstanceOfType", finalMessage); + StructuredAssertionMessage msg = expectedType is null + ? new("Cannot check type because the expected type argument is null.") + : new($"Expected value to be of type {expectedType.Name} (or derived)."); + msg.WithUserMessage(userMessage); + + if (expectedType is not null) + { + string actualTypeText = value?.GetType().ToString() ?? "null"; + EvidenceBlock evidence = EvidenceBlock.Create() + .AddLine("expected type:", $"{expectedType} (or derived)") + .AddLine(value is null ? "actual:" : "actual type:", actualTypeText); + msg.WithEvidence(evidence) + .WithExpectedAndActual($"{expectedType} (or derived)", actualTypeText); + } + + msg.WithCallSiteExpression(FormatCallSiteExpression("Assert.IsInstanceOfType", valueExpression, "")); + ReportAssertFailed(msg); } /// @@ -374,7 +377,7 @@ public static void IsNotInstanceOfType(object? value, [NotNull] Type? wrongType, { if (IsNotInstanceOfTypeFailing(value, wrongType)) { - ReportAssertIsNotInstanceOfTypeFailed(value, wrongType, BuildUserMessageForValueExpression(message, valueExpression)); + ReportAssertIsNotInstanceOfTypeFailed(value, wrongType, message, valueExpression); } } @@ -407,19 +410,24 @@ private static bool IsNotInstanceOfTypeFailing(object? value, [NotNullWhen(false (value is not null && wrongType.IsInstanceOfType(value)); [DoesNotReturn] - private static void ReportAssertIsNotInstanceOfTypeFailed(object? value, Type? wrongType, string userMessage) + private static void ReportAssertIsNotInstanceOfTypeFailed(object? value, Type? wrongType, string? userMessage, string valueExpression) { - string finalMessage = userMessage; + StructuredAssertionMessage msg = wrongType is null + ? new("Cannot check type because the not-expected type argument is null.") + : new($"Expected value to not be of type {wrongType.Name} (or derived)."); + msg.WithUserMessage(userMessage); + if (wrongType is not null) { - finalMessage = string.Format( - CultureInfo.CurrentCulture, - FrameworkMessages.IsNotInstanceOfFailMsg, - userMessage, - wrongType.ToString(), - value!.GetType().ToString()); + string actualTypeText = value?.GetType().ToString() ?? "null"; + EvidenceBlock evidence = EvidenceBlock.Create() + .AddLine("not expected type:", $"{wrongType} (or derived)") + .AddLine("actual type:", actualTypeText); + msg.WithEvidence(evidence) + .WithExpectedAndActual($"{wrongType} (or derived)", actualTypeText); } - ReportAssertFailed("Assert.IsNotInstanceOfType", finalMessage); + msg.WithCallSiteExpression(FormatCallSiteExpression("Assert.IsNotInstanceOfType", valueExpression, "")); + ReportAssertFailed(msg); } } diff --git a/src/TestFramework/TestFramework/Assertions/Assert.cs b/src/TestFramework/TestFramework/Assertions/Assert.cs index e6aa385347..c78d6b815e 100644 --- a/src/TestFramework/TestFramework/Assertions/Assert.cs +++ b/src/TestFramework/TestFramework/Assertions/Assert.cs @@ -179,23 +179,49 @@ internal static void ThrowAssertFailed(StructuredAssertionMessage structuredMess /// /// Formats a call-site expression for display at the bottom of a structured assertion message. /// When the expression is empty, the call-site is omitted. When the expression contains newlines, - /// it is replaced with a <paramName> placeholder. + /// it is replaced with the supplied placeholder (either a full <placeholder> or a raw parameter name). /// - internal static string? FormatCallSiteExpression(string assertionMethodName, string expression, string paramName) + internal static string? FormatCallSiteExpression(string assertionMethodName, string expression, string placeholderOrParamName = "") { if (string.IsNullOrWhiteSpace(expression)) { return null; } - // If expression contains newlines (multiline constant), replace with placeholder per RFC - string arg = expression.IndexOf('\n') >= 0 || expression.IndexOf('\r') >= 0 - ? $"<{paramName}>" - : expression; - + string arg = IsMultiline(expression) ? NormalizeCallSitePlaceholder(placeholderOrParamName) : expression; return $"{assertionMethodName}({arg})"; } + /// + /// Formats a call-site expression for display at the bottom of a structured assertion message, + /// using two captured expressions. Multiline expressions are replaced with the supplied placeholders. + /// When only one expression is empty/whitespace, its placeholder is used so the partial call site is still shown; + /// only when both expressions are empty/whitespace is the entire call-site line suppressed. + /// + internal static string? FormatCallSiteExpression(string assertionMethodName, string expression1, string expression2, string placeholder1 = "", string placeholder2 = "") + { + bool empty1 = string.IsNullOrWhiteSpace(expression1); + bool empty2 = string.IsNullOrWhiteSpace(expression2); + if (empty1 && empty2) + { + return null; + } + + string arg1 = empty1 || IsMultiline(expression1) ? NormalizeCallSitePlaceholder(placeholder1) : expression1; + string arg2 = empty2 || IsMultiline(expression2) ? NormalizeCallSitePlaceholder(placeholder2) : expression2; + + return $"{assertionMethodName}({arg1}, {arg2})"; + } + + // string.Contains(char) is not available on netstandard2.0 / net462, so use IndexOf to check for newline characters. + private static bool IsMultiline(string expression) + => expression.IndexOf('\n') >= 0 || expression.IndexOf('\r') >= 0; + + private static string NormalizeCallSitePlaceholder(string placeholderOrParamName) + => placeholderOrParamName.Length > 1 && placeholderOrParamName[0] == '<' && placeholderOrParamName[placeholderOrParamName.Length - 1] == '>' + ? placeholderOrParamName + : $"<{placeholderOrParamName}>"; + private static string FormatAssertionFailed(string assertionName, string? message) { string failedMessage = string.Format(CultureInfo.CurrentCulture, FrameworkMessages.AssertionFailed, assertionName); diff --git a/src/TestFramework/TestFramework/Assertions/AssertionValueRenderer.cs b/src/TestFramework/TestFramework/Assertions/AssertionValueRenderer.cs index e4a40a153c..fd34336624 100644 --- a/src/TestFramework/TestFramework/Assertions/AssertionValueRenderer.cs +++ b/src/TestFramework/TestFramework/Assertions/AssertionValueRenderer.cs @@ -19,9 +19,22 @@ internal static string RenderValue(object? value) bool b => b ? "true" : "false", char c => RenderChar(c), IEnumerable enumerable => RenderCollection(enumerable), - _ => value.ToString() ?? value.GetType().FullName ?? value.GetType().Name, + _ => RenderObject(value), }; + private static string RenderObject(object value) + { + // Guard against user types whose ToString() throws so the original assertion failure is preserved. + try + { + return value.ToString() ?? value.GetType().FullName ?? value.GetType().Name; + } + catch (Exception ex) + { + return $"{value.GetType().FullName ?? value.GetType().Name} (ToString threw {ex.GetType().Name})"; + } + } + /// /// Renders a string value with double quotes and escape sequences for control characters. /// diff --git a/test/UnitTests/TestFramework.UnitTests/Assertions/AssertTests.AreSame.cs b/test/UnitTests/TestFramework.UnitTests/Assertions/AssertTests.AreSame.cs index fd7569a444..de766093f7 100644 --- a/test/UnitTests/TestFramework.UnitTests/Assertions/AssertTests.AreSame.cs +++ b/test/UnitTests/TestFramework.UnitTests/Assertions/AssertTests.AreSame.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using AwesomeAssertions; @@ -16,7 +16,16 @@ public void AreSame_PassSameObject_ShouldPass() public void AreSame_PassDifferentObject_ShouldFail() { Action action = () => Assert.AreSame(new object(), new object()); - action.Should().Throw().WithMessage("Assert.AreSame failed. 'expected' expression: 'new object()', 'actual' expression: 'new object()'."); + action.Should().Throw() + .WithMessage( + """ + Assertion failed. Expected both values to refer to the same object. + + expected: System.Object (hash: 0x*) + actual: System.Object (hash: 0x*) + + Assert.AreSame(new object(), new object()) + """); } public void AreSame_StringMessage_PassSameObject_ShouldPass() @@ -28,7 +37,17 @@ public void AreSame_StringMessage_PassSameObject_ShouldPass() public void AreSame_StringMessage_PassDifferentObject_ShouldFail() { Action action = () => Assert.AreSame(new object(), new object(), "User-provided message"); - action.Should().Throw().WithMessage("Assert.AreSame failed. 'expected' expression: 'new object()', 'actual' expression: 'new object()'. User-provided message"); + action.Should().Throw() + .WithMessage( + """ + Assertion failed. Expected both values to refer to the same object. + User-provided message + + expected: System.Object (hash: 0x*) + actual: System.Object (hash: 0x*) + + Assert.AreSame(new object(), new object()) + """); } public void AreSame_InterpolatedString_PassSameObject_ShouldPass() @@ -43,7 +62,16 @@ public async Task AreSame_InterpolatedString_PassDifferentObject_ShouldFail() DummyClassTrackingToStringCalls o = new(); DateTime dateTime = DateTime.Now; Func action = async () => Assert.AreSame(new object(), new object(), $"User-provided message. {o}, {o,35}, {await GetHelloStringAsync()}, {new DummyIFormattable()}, {dateTime:tt}, {dateTime,5:tt}"); - (await action.Should().ThrowAsync()).WithMessage($"Assert.AreSame failed. 'expected' expression: 'new object()', 'actual' expression: 'new object()'. User-provided message. DummyClassTrackingToStringCalls, DummyClassTrackingToStringCalls, Hello, DummyIFormattable.ToString(), {string.Format(null, "{0:tt}", dateTime)}, {string.Format(null, "{0,5:tt}", dateTime)}"); + (await action.Should().ThrowAsync()).WithMessage( + $$""" + Assertion failed. Expected both values to refer to the same object. + User-provided message. DummyClassTrackingToStringCalls, DummyClassTrackingToStringCalls, Hello, DummyIFormattable.ToString(), {{string.Format(null, "{0:tt}", dateTime)}}, {{string.Format(null, "{0,5:tt}", dateTime)}} + + expected: System.Object (hash: 0x*) + actual: System.Object (hash: 0x*) + + Assert.AreSame(new object(), new object()) + """); o.WasToStringCalled.Should().BeTrue(); } @@ -53,68 +81,169 @@ public void AreNotSame_PassDifferentObject_ShouldPass() public void AreSame_BothAreValueTypes_ShouldFailWithSpecializedMessage() { Action action = () => Assert.AreSame(1, 1); - action.Should().Throw().WithMessage("Assert.AreSame failed. Do not pass value types to AreSame(). Values converted to Object will never be the same. Consider using AreEqual(). 'expected' expression: '1', 'actual' expression: '1'."); + action.Should().Throw() + .WithMessage( + """ + Assertion failed. Expected both values to refer to the same object. + Do not pass value types to AreSame — value types are boxed on each call, so references will never be the same. + + Assert.AreSame(1, 1) + """); } public void AreSame_StringMessage_BothAreValueTypes_ShouldFailWithSpecializedMessage() { Action action = () => Assert.AreSame(1, 1, "User-provided message"); - action.Should().Throw().WithMessage("Assert.AreSame failed. Do not pass value types to AreSame(). Values converted to Object will never be the same. Consider using AreEqual(). 'expected' expression: '1', 'actual' expression: '1'. User-provided message"); + action.Should().Throw() + .WithMessage( + """ + Assertion failed. Expected both values to refer to the same object. + Do not pass value types to AreSame — value types are boxed on each call, so references will never be the same. + User-provided message + + Assert.AreSame(1, 1) + """); } public void AreSame_InterpolatedString_BothAreValueTypes_ShouldFailWithSpecializedMessage() { Action action = () => Assert.AreSame(1, 1, $"User-provided message {new object().GetType()}"); - action.Should().Throw().WithMessage("Assert.AreSame failed. Do not pass value types to AreSame(). Values converted to Object will never be the same. Consider using AreEqual(). 'expected' expression: '1', 'actual' expression: '1'. User-provided message System.Object"); + action.Should().Throw() + .WithMessage( + """ + Assertion failed. Expected both values to refer to the same object. + Do not pass value types to AreSame — value types are boxed on each call, so references will never be the same. + User-provided message System.Object + + Assert.AreSame(1, 1) + """); } public void AreSame_ExpectedNull_ShouldFailWithNullMessage() { object? expected = null; Action action = () => Assert.AreSame(expected, new object()); - action.Should().Throw().WithMessage("Assert.AreSame failed. Expected is . 'expected' expression: 'expected', 'actual' expression: 'new object()'."); + action.Should().Throw() + .WithMessage( + """ + Assertion failed. Expected both values to refer to the same object. + + expected: null + actual: System.Object (hash: 0x*) + + Assert.AreSame(expected, new object()) + """); } public void AreSame_ActualNull_ShouldFailWithNullMessage() { object? actual = null; Action action = () => Assert.AreSame(new object(), actual); - action.Should().Throw().WithMessage("Assert.AreSame failed. Actual is . 'expected' expression: 'new object()', 'actual' expression: 'actual'."); + action.Should().Throw() + .WithMessage( + """ + Assertion failed. Expected both values to refer to the same object. + + expected: System.Object (hash: 0x*) + actual: null + + Assert.AreSame(new object(), actual) + """); } public void AreSame_StringMessage_ExpectedNull_ShouldFailWithNullMessage() { object? expected = null; Action action = () => Assert.AreSame(expected, new object(), "User-provided message"); - action.Should().Throw().WithMessage("Assert.AreSame failed. Expected is . 'expected' expression: 'expected', 'actual' expression: 'new object()'. User-provided message"); + action.Should().Throw() + .WithMessage( + """ + Assertion failed. Expected both values to refer to the same object. + User-provided message + + expected: null + actual: System.Object (hash: 0x*) + + Assert.AreSame(expected, new object()) + """); } public void AreSame_StringMessage_ActualNull_ShouldFailWithNullMessage() { object? actual = null; Action action = () => Assert.AreSame(new object(), actual, "User-provided message"); - action.Should().Throw().WithMessage("Assert.AreSame failed. Actual is . 'expected' expression: 'new object()', 'actual' expression: 'actual'. User-provided message"); + action.Should().Throw() + .WithMessage( + """ + Assertion failed. Expected both values to refer to the same object. + User-provided message + + expected: System.Object (hash: 0x*) + actual: null + + Assert.AreSame(new object(), actual) + """); } public void AreSame_InterpolatedString_ExpectedNull_ShouldFailWithNullMessage() { object? expected = null; Action action = () => Assert.AreSame(expected, new object(), $"User-provided message {new object().GetType()}"); - action.Should().Throw().WithMessage("Assert.AreSame failed. Expected is . 'expected' expression: 'expected', 'actual' expression: 'new object()'. User-provided message System.Object"); + action.Should().Throw() + .WithMessage( + """ + Assertion failed. Expected both values to refer to the same object. + User-provided message System.Object + + expected: null + actual: System.Object (hash: 0x*) + + Assert.AreSame(expected, new object()) + """); } public void AreSame_InterpolatedString_ActualNull_ShouldFailWithNullMessage() { object? actual = null; Action action = () => Assert.AreSame(new object(), actual, $"User-provided message {new object().GetType()}"); - action.Should().Throw().WithMessage("Assert.AreSame failed. Actual is . 'expected' expression: 'new object()', 'actual' expression: 'actual'. User-provided message System.Object"); + action.Should().Throw() + .WithMessage( + """ + Assertion failed. Expected both values to refer to the same object. + User-provided message System.Object + + expected: System.Object (hash: 0x*) + actual: null + + Assert.AreSame(new object(), actual) + """); + } + + public void AreSame_ShouldPopulateExpectedAndActualPayloadOnFailure() + { + object expected = new(); + object actual = new(); + Action action = () => Assert.AreSame(expected, actual); + + AssertFailedException ex = action.Should().Throw().Which; + ex.ExpectedText.Should().Be($"System.Object (hash: 0x{System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(expected):X})"); + ex.ActualText.Should().Be($"System.Object (hash: 0x{System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(actual):X})"); + ex.Data["assert.expected"].Should().Be(ex.ExpectedText); + ex.Data["assert.actual"].Should().Be(ex.ActualText); } public void AreNotSame_PassSameObject_ShouldFail() { object o = new(); Action action = () => Assert.AreNotSame(o, o); - action.Should().Throw().WithMessage("Assert.AreNotSame failed. 'notExpected' expression: 'o', 'actual' expression: 'o'."); + action.Should().Throw() + .WithMessage( + """ + Assertion failed. Expected values to refer to different objects. + Both values refer to the same object. + + Assert.AreNotSame(o, o) + """); } public void AreNotSame_StringMessage_PassDifferentObject_ShouldPass() @@ -124,7 +253,15 @@ public void AreNotSame_StringMessage_PassSameObject_ShouldFail() { object o = new(); Action action = () => Assert.AreNotSame(o, o, "User-provided message"); - action.Should().Throw().WithMessage("Assert.AreNotSame failed. 'notExpected' expression: 'o', 'actual' expression: 'o'. User-provided message"); + action.Should().Throw() + .WithMessage( + """ + Assertion failed. Expected values to refer to different objects. + Both values refer to the same object. + User-provided message + + Assert.AreNotSame(o, o) + """); } public void AreNotSame_InterpolatedString_PassDifferentObject_ShouldPass() @@ -139,7 +276,14 @@ public async Task AreNotSame_InterpolatedString_PassSameObject_ShouldFail() DummyClassTrackingToStringCalls o = new(); DateTime dateTime = DateTime.Now; Func action = async () => Assert.AreNotSame(o, o, $"User-provided message. {o}, {o,35}, {await GetHelloStringAsync()}, {new DummyIFormattable()}, {dateTime:tt}, {dateTime,5:tt}"); - (await action.Should().ThrowAsync()).WithMessage($"Assert.AreNotSame failed. 'notExpected' expression: 'o', 'actual' expression: 'o'. User-provided message. DummyClassTrackingToStringCalls, DummyClassTrackingToStringCalls, Hello, DummyIFormattable.ToString(), {string.Format(null, "{0:tt}", dateTime)}, {string.Format(null, "{0,5:tt}", dateTime)}"); + (await action.Should().ThrowAsync()).WithMessage( + $$""" + Assertion failed. Expected values to refer to different objects. + Both values refer to the same object. + User-provided message. DummyClassTrackingToStringCalls, DummyClassTrackingToStringCalls, Hello, DummyIFormattable.ToString(), {{string.Format(null, "{0:tt}", dateTime)}}, {{string.Format(null, "{0,5:tt}", dateTime)}} + + Assert.AreNotSame(o, o) + """); o.WasToStringCalled.Should().BeTrue(); } @@ -148,7 +292,14 @@ public void AreNotSame_BothNull_ShouldFailWithNullMessage() object? notExpected = null; object? actual = null; Action action = () => Assert.AreNotSame(notExpected, actual); - action.Should().Throw().WithMessage("Assert.AreNotSame failed. Both values are . 'notExpected' expression: 'notExpected', 'actual' expression: 'actual'."); + action.Should().Throw() + .WithMessage( + """ + Assertion failed. Expected values to refer to different objects. + Both values are null. + + Assert.AreNotSame(notExpected, actual) + """); } public void AreNotSame_StringMessage_BothNull_ShouldFailWithNullMessage() @@ -156,7 +307,15 @@ public void AreNotSame_StringMessage_BothNull_ShouldFailWithNullMessage() object? notExpected = null; object? actual = null; Action action = () => Assert.AreNotSame(notExpected, actual, "User-provided message"); - action.Should().Throw().WithMessage("Assert.AreNotSame failed. Both values are . 'notExpected' expression: 'notExpected', 'actual' expression: 'actual'. User-provided message"); + action.Should().Throw() + .WithMessage( + """ + Assertion failed. Expected values to refer to different objects. + Both values are null. + User-provided message + + Assert.AreNotSame(notExpected, actual) + """); } public void AreNotSame_InterpolatedString_BothNull_ShouldFailWithNullMessage() @@ -164,6 +323,14 @@ public void AreNotSame_InterpolatedString_BothNull_ShouldFailWithNullMessage() object? notExpected = null; object? actual = null; Action action = () => Assert.AreNotSame(notExpected, actual, $"User-provided message {new object().GetType()}"); - action.Should().Throw().WithMessage("Assert.AreNotSame failed. Both values are . 'notExpected' expression: 'notExpected', 'actual' expression: 'actual'. User-provided message System.Object"); + action.Should().Throw() + .WithMessage( + """ + Assertion failed. Expected values to refer to different objects. + Both values are null. + User-provided message System.Object + + Assert.AreNotSame(notExpected, actual) + """); } } diff --git a/test/UnitTests/TestFramework.UnitTests/Assertions/AssertTests.FormatCallSiteExpressionTests.cs b/test/UnitTests/TestFramework.UnitTests/Assertions/AssertTests.FormatCallSiteExpressionTests.cs new file mode 100644 index 0000000000..7351bd86e9 --- /dev/null +++ b/test/UnitTests/TestFramework.UnitTests/Assertions/AssertTests.FormatCallSiteExpressionTests.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using AwesomeAssertions; + +namespace Microsoft.VisualStudio.TestPlatform.TestFramework.UnitTests; + +public partial class AssertTests +{ + public void FormatCallSiteExpression_SingleArg_NormalExpression_ReturnsExpression() + => Assert.FormatCallSiteExpression("Assert.X", "value").Should().Be("Assert.X(value)"); + + public void FormatCallSiteExpression_SingleArg_NullOrWhitespaceExpression_ReturnsNull() + { + Assert.FormatCallSiteExpression("Assert.X", string.Empty).Should().BeNull(); + Assert.FormatCallSiteExpression("Assert.X", " ").Should().BeNull(); + } + + public void FormatCallSiteExpression_SingleArg_MultilineExpression_UsesPlaceholder() + { + Assert.FormatCallSiteExpression("Assert.X", "foo\nbar").Should().Be("Assert.X()"); + Assert.FormatCallSiteExpression("Assert.X", "foo\r\nbar").Should().Be("Assert.X()"); + Assert.FormatCallSiteExpression("Assert.X", "foo\rbar").Should().Be("Assert.X()"); + } + + public void FormatCallSiteExpression_TwoArgs_NormalExpressions_ReturnsExpressions() + => Assert.FormatCallSiteExpression("Assert.X", expression1: "a", expression2: "b").Should().Be("Assert.X(a, b)"); + + public void FormatCallSiteExpression_TwoArgs_BothEmpty_ReturnsNull() + => Assert.FormatCallSiteExpression("Assert.X", expression1: string.Empty, expression2: " ").Should().BeNull(); + + public void FormatCallSiteExpression_TwoArgs_FirstEmpty_UsesPlaceholderForFirst() + => Assert.FormatCallSiteExpression("Assert.X", expression1: string.Empty, expression2: "b").Should().Be("Assert.X(, b)"); + + public void FormatCallSiteExpression_TwoArgs_SecondEmpty_UsesPlaceholderForSecond() + => Assert.FormatCallSiteExpression("Assert.X", expression1: "a", expression2: string.Empty).Should().Be("Assert.X(a, )"); + + public void FormatCallSiteExpression_TwoArgs_FirstMultiline_UsesPlaceholderForFirst() + => Assert.FormatCallSiteExpression("Assert.X", expression1: "a\nb", expression2: "c").Should().Be("Assert.X(, c)"); + + public void FormatCallSiteExpression_TwoArgs_SecondMultiline_UsesPlaceholderForSecond() + => Assert.FormatCallSiteExpression("Assert.X", expression1: "a", expression2: "b\nc").Should().Be("Assert.X(a, )"); + + public void FormatCallSiteExpression_TwoArgs_BothMultiline_UsesBothPlaceholders() + => Assert.FormatCallSiteExpression("Assert.X", expression1: "a\nb", expression2: "c\nd").Should().Be("Assert.X(, )"); +} diff --git a/test/UnitTests/TestFramework.UnitTests/Assertions/AssertTests.IsExactInstanceOfTypeTests.cs b/test/UnitTests/TestFramework.UnitTests/Assertions/AssertTests.IsExactInstanceOfTypeTests.cs index aec566af37..e6469596b8 100644 --- a/test/UnitTests/TestFramework.UnitTests/Assertions/AssertTests.IsExactInstanceOfTypeTests.cs +++ b/test/UnitTests/TestFramework.UnitTests/Assertions/AssertTests.IsExactInstanceOfTypeTests.cs @@ -12,21 +12,42 @@ public void ExactInstanceOfTypeShouldFailWhenValueIsNull() { Action action = () => Assert.IsExactInstanceOfType(null, typeof(AssertTests)); action.Should().Throw() - .WithMessage("Assert.IsExactInstanceOfType failed. 'value' expression: 'null'. Expected exact type:. Actual type:."); + .WithMessage( + """ + Assertion failed. Expected value to be exactly of type AssertTests. + + expected type: Microsoft.VisualStudio.TestPlatform.TestFramework.UnitTests.AssertTests + actual: null + + Assert.IsExactInstanceOfType(null) + """); } public void ExactInstanceOfTypeShouldFailWhenTypeIsNull() { Action action = () => Assert.IsExactInstanceOfType(5, null); action.Should().Throw() - .WithMessage("Assert.IsExactInstanceOfType failed. 'value' expression: '5'."); + .WithMessage( + """ + Assertion failed. Cannot check type because the expected type argument is null. + + Assert.IsExactInstanceOfType(5) + """); } public void ExactInstanceOfTypeShouldFailWhenTypeIsMismatched() { Action action = () => Assert.IsExactInstanceOfType(5, typeof(string)); action.Should().Throw() - .WithMessage("Assert.IsExactInstanceOfType failed. 'value' expression: '5'. Expected exact type:. Actual type:."); + .WithMessage( + """ + Assertion failed. Expected value to be exactly of type String. + + expected type: System.String + actual type: System.Int32 + + Assert.IsExactInstanceOfType(5) + """); } public void ExactInstanceOfTypeShouldPassOnSameInstance() => Assert.IsExactInstanceOfType(5, typeof(int)); @@ -35,7 +56,15 @@ public void ExactInstanceOfTypeShouldFailOnHigherInstance() { Action action = () => Assert.IsExactInstanceOfType(5, typeof(object)); action.Should().Throw() - .WithMessage("Assert.IsExactInstanceOfType failed. 'value' expression: '5'. Expected exact type:. Actual type:."); + .WithMessage( + """ + Assertion failed. Expected value to be exactly of type Object. + + expected type: System.Object + actual type: System.Int32 + + Assert.IsExactInstanceOfType(5) + """); } public void ExactInstanceOfTypeShouldFailOnDerivedType() @@ -43,7 +72,15 @@ public void ExactInstanceOfTypeShouldFailOnDerivedType() object x = new MemoryStream(); Action action = () => Assert.IsExactInstanceOfType(x, typeof(Stream)); action.Should().Throw() - .WithMessage("Assert.IsExactInstanceOfType failed. 'value' expression: 'x'. Expected exact type:. Actual type:."); + .WithMessage( + """ + Assertion failed. Expected value to be exactly of type Stream. + + expected type: System.IO.Stream + actual type: System.IO.MemoryStream + + Assert.IsExactInstanceOfType(x) + """); } public void ExactInstanceOfTypeShouldPassOnExactType() @@ -56,21 +93,45 @@ public void ExactInstanceOfType_WithStringMessage_ShouldFailWhenValueIsNull() { Action action = () => Assert.IsExactInstanceOfType(null, typeof(AssertTests), "User-provided message"); action.Should().Throw() - .WithMessage("Assert.IsExactInstanceOfType failed. 'value' expression: 'null'. User-provided message Expected exact type:. Actual type:."); + .WithMessage( + """ + Assertion failed. Expected value to be exactly of type AssertTests. + User-provided message + + expected type: Microsoft.VisualStudio.TestPlatform.TestFramework.UnitTests.AssertTests + actual: null + + Assert.IsExactInstanceOfType(null) + """); } public void ExactInstanceOfType_WithStringMessage_ShouldFailWhenTypeIsNull() { Action action = () => Assert.IsExactInstanceOfType(5, null, "User-provided message"); action.Should().Throw() - .WithMessage("Assert.IsExactInstanceOfType failed. 'value' expression: '5'. User-provided message"); + .WithMessage( + """ + Assertion failed. Cannot check type because the expected type argument is null. + User-provided message + + Assert.IsExactInstanceOfType(5) + """); } public void ExactInstanceOfType_WithStringMessage_ShouldFailWhenTypeIsMismatched() { Action action = () => Assert.IsExactInstanceOfType(5, typeof(string), "User-provided message"); action.Should().Throw() - .WithMessage("Assert.IsExactInstanceOfType failed. 'value' expression: '5'. User-provided message Expected exact type:. Actual type:."); + .WithMessage( + """ + Assertion failed. Expected value to be exactly of type String. + User-provided message + + expected type: System.String + actual type: System.Int32 + + Assert.IsExactInstanceOfType(5) + """); } public void ExactInstanceOfType_WithStringMessage_ShouldPassWhenTypeIsCorrect() @@ -82,7 +143,16 @@ public async Task ExactInstanceOfType_WithInterpolatedString_ShouldFailWhenValue DateTime dateTime = DateTime.Now; Func action = async () => Assert.IsExactInstanceOfType(null, typeof(AssertTests), $"User-provided message. {o}, {o,35}, {await GetHelloStringAsync()}, {new DummyIFormattable()}, {dateTime:tt}, {dateTime,5:tt}"); (await action.Should().ThrowAsync()) - .WithMessage($"Assert.IsExactInstanceOfType failed. 'value' expression: 'null'. User-provided message. DummyClassTrackingToStringCalls, DummyClassTrackingToStringCalls, Hello, DummyIFormattable.ToString(), {string.Format(null, "{0:tt}", dateTime)}, {string.Format(null, "{0,5:tt}", dateTime)} Expected exact type:. Actual type:."); + .WithMessage( + $$""" + Assertion failed. Expected value to be exactly of type AssertTests. + User-provided message. DummyClassTrackingToStringCalls, DummyClassTrackingToStringCalls, Hello, DummyIFormattable.ToString(), {{string.Format(null, "{0:tt}", dateTime)}}, {{string.Format(null, "{0,5:tt}", dateTime)}} + + expected type: Microsoft.VisualStudio.TestPlatform.TestFramework.UnitTests.AssertTests + actual: null + + Assert.IsExactInstanceOfType(null) + """); o.WasToStringCalled.Should().BeTrue(); } @@ -91,7 +161,13 @@ public void ExactInstanceOfType_WithInterpolatedString_ShouldFailWhenTypeIsNull( DummyClassTrackingToStringCalls o = new(); Action action = () => Assert.IsExactInstanceOfType(5, null, $"User-provided message {o}"); action.Should().Throw() - .WithMessage("Assert.IsExactInstanceOfType failed. 'value' expression: '5'. User-provided message DummyClassTrackingToStringCalls"); + .WithMessage( + """ + Assertion failed. Cannot check type because the expected type argument is null. + User-provided message DummyClassTrackingToStringCalls + + Assert.IsExactInstanceOfType(5) + """); o.WasToStringCalled.Should().BeTrue(); } @@ -100,7 +176,16 @@ public void ExactInstanceOfType_WithInterpolatedString_ShouldFailWhenTypeIsMisma DummyClassTrackingToStringCalls o = new(); Action action = () => Assert.IsExactInstanceOfType(5, typeof(string), $"User-provided message {o}"); action.Should().Throw() - .WithMessage("Assert.IsExactInstanceOfType failed. 'value' expression: '5'. User-provided message DummyClassTrackingToStringCalls Expected exact type:. Actual type:."); + .WithMessage( + """ + Assertion failed. Expected value to be exactly of type String. + User-provided message DummyClassTrackingToStringCalls + + expected type: System.String + actual type: System.Int32 + + Assert.IsExactInstanceOfType(5) + """); o.WasToStringCalled.Should().BeTrue(); } @@ -134,21 +219,45 @@ public void ExactInstanceNotOfTypeShouldFailOnExactType() object x = new MemoryStream(); Action action = () => Assert.IsNotExactInstanceOfType(x, typeof(MemoryStream)); action.Should().Throw() - .WithMessage("Assert.IsNotExactInstanceOfType failed. Wrong exact Type:. Actual type:. 'value' expression: 'x'."); + .WithMessage( + """ + Assertion failed. Expected value to not be exactly of type MemoryStream. + + not expected type: System.IO.MemoryStream + actual type: System.IO.MemoryStream + + Assert.IsNotExactInstanceOfType(x) + """); } public void IsExactInstanceOfTypeUsingGenericType_WhenValueIsNull_Fails() { Action action = () => Assert.IsExactInstanceOfType(null); action.Should().Throw() - .WithMessage("Assert.IsExactInstanceOfType failed. 'value' expression: 'null'. Expected exact type:. Actual type:."); + .WithMessage( + """ + Assertion failed. Expected value to be exactly of type AssertTests. + + expected type: Microsoft.VisualStudio.TestPlatform.TestFramework.UnitTests.AssertTests + actual: null + + Assert.IsExactInstanceOfType(null) + """); } public void IsExactInstanceOfTypeUsingGenericType_WhenTypeMismatch_Fails() { Action action = () => Assert.IsExactInstanceOfType(5); action.Should().Throw() - .WithMessage("Assert.IsExactInstanceOfType failed. 'value' expression: '5'. Expected exact type:. Actual type:."); + .WithMessage( + """ + Assertion failed. Expected value to be exactly of type String. + + expected type: System.String + actual type: System.Int32 + + Assert.IsExactInstanceOfType(5) + """); } public void IsExactInstanceOfTypeUsingGenericType_WhenDerivedType_Fails() @@ -156,7 +265,15 @@ public void IsExactInstanceOfTypeUsingGenericType_WhenDerivedType_Fails() object x = new MemoryStream(); Action action = () => Assert.IsExactInstanceOfType(x); action.Should().Throw() - .WithMessage("Assert.IsExactInstanceOfType failed. 'value' expression: 'x'. Expected exact type:. Actual type:."); + .WithMessage( + """ + Assertion failed. Expected value to be exactly of type Stream. + + expected type: System.IO.Stream + actual type: System.IO.MemoryStream + + Assert.IsExactInstanceOfType(x) + """); } public void IsExactInstanceOfTypeUsingGenericType_OnSameInstance_DoesNotThrow() => Assert.IsExactInstanceOfType(5); @@ -178,7 +295,15 @@ public void IsExactInstanceOfTypeUsingGenericType_OnHigherInstance_Fails() { Action action = () => Assert.IsExactInstanceOfType(5); action.Should().Throw() - .WithMessage("Assert.IsExactInstanceOfType failed. 'value' expression: '5'. Expected exact type:. Actual type:."); + .WithMessage( + """ + Assertion failed. Expected value to be exactly of type Object. + + expected type: System.Object + actual type: System.Int32 + + Assert.IsExactInstanceOfType(5) + """); } public void IsExactInstanceOfTypeUsingGenericTypeWithReturn_OnExactType_DoesNotThrow() @@ -205,7 +330,15 @@ public void IsNotExactInstanceOfTypeUsingGenericType_OnExactType_Fails() object x = new MemoryStream(); Action action = () => Assert.IsNotExactInstanceOfType(x); action.Should().Throw() - .WithMessage("Assert.IsNotExactInstanceOfType failed. Wrong exact Type:. Actual type:. 'value' expression: 'x'."); + .WithMessage( + """ + Assertion failed. Expected value to not be exactly of type MemoryStream. + + not expected type: System.IO.MemoryStream + actual type: System.IO.MemoryStream + + Assert.IsNotExactInstanceOfType(x) + """); } public void IsExactInstanceOfType_WhenNonNullNullableValue_LearnNonNull() @@ -277,4 +410,41 @@ public void IsNotExactInstanceOfType_WhenNonNullNullableTypeAndMessage_LearnNonN Assert.IsNotExactInstanceOfType(new object(), intType, "my message"); _ = intType.ToString(); // no warning about possible null } + + public void IsExactInstanceOfType_OnFailure_ShouldPopulateExpectedAndActualPayload() + { + try + { + Assert.IsExactInstanceOfType(5, typeof(string)); + } + catch (AssertFailedException ex) + { + ex.ExpectedText.Should().Be("System.String"); + ex.ActualText.Should().Be("System.Int32"); + ex.Data["assert.expected"].Should().Be("System.String"); + ex.Data["assert.actual"].Should().Be("System.Int32"); + return; + } + + throw new AssertFailedException("Expected AssertFailedException was not thrown."); + } + + public void IsNotExactInstanceOfType_OnFailure_ShouldPopulateExpectedAndActualPayload() + { + object x = new MemoryStream(); + try + { + Assert.IsNotExactInstanceOfType(x, typeof(MemoryStream)); + } + catch (AssertFailedException ex) + { + ex.ExpectedText.Should().Be("System.IO.MemoryStream"); + ex.ActualText.Should().Be("System.IO.MemoryStream"); + ex.Data["assert.expected"].Should().Be("System.IO.MemoryStream"); + ex.Data["assert.actual"].Should().Be("System.IO.MemoryStream"); + return; + } + + throw new AssertFailedException("Expected AssertFailedException was not thrown."); + } } diff --git a/test/UnitTests/TestFramework.UnitTests/Assertions/AssertTests.IsInstanceOfTypeTests.cs b/test/UnitTests/TestFramework.UnitTests/Assertions/AssertTests.IsInstanceOfTypeTests.cs index 8dbe699579..210d3edb14 100644 --- a/test/UnitTests/TestFramework.UnitTests/Assertions/AssertTests.IsInstanceOfTypeTests.cs +++ b/test/UnitTests/TestFramework.UnitTests/Assertions/AssertTests.IsInstanceOfTypeTests.cs @@ -12,21 +12,42 @@ public void InstanceOfTypeShouldFailWhenValueIsNull() { Action action = () => Assert.IsInstanceOfType(null, typeof(AssertTests)); action.Should().Throw() - .WithMessage("Assert.IsInstanceOfType failed. 'value' expression: 'null'. Expected type:. Actual type:."); + .WithMessage( + """ + Assertion failed. Expected value to be of type AssertTests (or derived). + + expected type: Microsoft.VisualStudio.TestPlatform.TestFramework.UnitTests.AssertTests (or derived) + actual: null + + Assert.IsInstanceOfType(null) + """); } public void InstanceOfTypeShouldFailWhenTypeIsNull() { Action action = () => Assert.IsInstanceOfType(5, null); action.Should().Throw() - .WithMessage("Assert.IsInstanceOfType failed. 'value' expression: '5'."); + .WithMessage( + """ + Assertion failed. Cannot check type because the expected type argument is null. + + Assert.IsInstanceOfType(5) + """); } public void InstanceOfTypeShouldFailWhenTypeIsMismatched() { Action action = () => Assert.IsInstanceOfType(5, typeof(string)); action.Should().Throw() - .WithMessage("Assert.IsInstanceOfType failed. 'value' expression: '5'. Expected type:. Actual type:."); + .WithMessage( + """ + Assertion failed. Expected value to be of type String (or derived). + + expected type: System.String (or derived) + actual type: System.Int32 + + Assert.IsInstanceOfType(5) + """); } public void InstanceOfTypeShouldPassOnSameInstance() => Assert.IsInstanceOfType(5, typeof(int)); @@ -37,21 +58,45 @@ public void InstanceOfType_WithStringMessage_ShouldFailWhenValueIsNull() { Action action = () => Assert.IsInstanceOfType(null, typeof(AssertTests), "User-provided message"); action.Should().Throw() - .WithMessage("Assert.IsInstanceOfType failed. 'value' expression: 'null'. User-provided message Expected type:. Actual type:."); + .WithMessage( + """ + Assertion failed. Expected value to be of type AssertTests (or derived). + User-provided message + + expected type: Microsoft.VisualStudio.TestPlatform.TestFramework.UnitTests.AssertTests (or derived) + actual: null + + Assert.IsInstanceOfType(null) + """); } public void InstanceOfType_WithStringMessage_ShouldFailWhenTypeIsNull() { Action action = () => Assert.IsInstanceOfType(5, null, "User-provided message"); action.Should().Throw() - .WithMessage("Assert.IsInstanceOfType failed. 'value' expression: '5'. User-provided message"); + .WithMessage( + """ + Assertion failed. Cannot check type because the expected type argument is null. + User-provided message + + Assert.IsInstanceOfType(5) + """); } public void InstanceOfType_WithStringMessage_ShouldFailWhenTypeIsMismatched() { Action action = () => Assert.IsInstanceOfType(5, typeof(string), "User-provided message"); action.Should().Throw() - .WithMessage("Assert.IsInstanceOfType failed. 'value' expression: '5'. User-provided message Expected type:. Actual type:."); + .WithMessage( + """ + Assertion failed. Expected value to be of type String (or derived). + User-provided message + + expected type: System.String (or derived) + actual type: System.Int32 + + Assert.IsInstanceOfType(5) + """); } public void InstanceOfType_WithStringMessage_ShouldPassWhenTypeIsCorrect() @@ -63,7 +108,16 @@ public async Task InstanceOfType_WithInterpolatedString_ShouldFailWhenValueIsNul DateTime dateTime = DateTime.Now; Func action = async () => Assert.IsInstanceOfType(null, typeof(AssertTests), $"User-provided message. {o}, {o,35}, {await GetHelloStringAsync()}, {new DummyIFormattable()}, {dateTime:tt}, {dateTime,5:tt}"); (await action.Should().ThrowAsync()) - .WithMessage($"Assert.IsInstanceOfType failed. 'value' expression: 'null'. User-provided message. DummyClassTrackingToStringCalls, DummyClassTrackingToStringCalls, Hello, DummyIFormattable.ToString(), {string.Format(null, "{0:tt}", dateTime)}, {string.Format(null, "{0,5:tt}", dateTime)} Expected type:. Actual type:."); + .WithMessage( + $$""" + Assertion failed. Expected value to be of type AssertTests (or derived). + User-provided message. DummyClassTrackingToStringCalls, DummyClassTrackingToStringCalls, Hello, DummyIFormattable.ToString(), {{string.Format(null, "{0:tt}", dateTime)}}, {{string.Format(null, "{0,5:tt}", dateTime)}} + + expected type: Microsoft.VisualStudio.TestPlatform.TestFramework.UnitTests.AssertTests (or derived) + actual: null + + Assert.IsInstanceOfType(null) + """); o.WasToStringCalled.Should().BeTrue(); } @@ -72,7 +126,13 @@ public void InstanceOfType_WithInterpolatedString_ShouldFailWhenTypeIsNull() DummyClassTrackingToStringCalls o = new(); Action action = () => Assert.IsInstanceOfType(5, null, $"User-provided message {o}"); action.Should().Throw() - .WithMessage("Assert.IsInstanceOfType failed. 'value' expression: '5'. User-provided message DummyClassTrackingToStringCalls"); + .WithMessage( + """ + Assertion failed. Cannot check type because the expected type argument is null. + User-provided message DummyClassTrackingToStringCalls + + Assert.IsInstanceOfType(5) + """); o.WasToStringCalled.Should().BeTrue(); } @@ -81,7 +141,16 @@ public void InstanceOfType_WithInterpolatedString_ShouldFailWhenTypeIsMismatched DummyClassTrackingToStringCalls o = new(); Action action = () => Assert.IsInstanceOfType(5, typeof(string), $"User-provided message {o}"); action.Should().Throw() - .WithMessage("Assert.IsInstanceOfType failed. 'value' expression: '5'. User-provided message DummyClassTrackingToStringCalls Expected type:. Actual type:."); + .WithMessage( + """ + Assertion failed. Expected value to be of type String (or derived). + User-provided message DummyClassTrackingToStringCalls + + expected type: System.String (or derived) + actual type: System.Int32 + + Assert.IsInstanceOfType(5) + """); o.WasToStringCalled.Should().BeTrue(); } @@ -108,14 +177,30 @@ public void IsInstanceOfTypeUsingGenericType_WhenValueIsNull_Fails() { Action action = () => Assert.IsInstanceOfType(null); action.Should().Throw() - .WithMessage("Assert.IsInstanceOfType failed. 'value' expression: 'null'. Expected type:. Actual type:."); + .WithMessage( + """ + Assertion failed. Expected value to be of type AssertTests (or derived). + + expected type: Microsoft.VisualStudio.TestPlatform.TestFramework.UnitTests.AssertTests (or derived) + actual: null + + Assert.IsInstanceOfType(null) + """); } public void IsInstanceOfTypeUsingGenericType_WhenTypeMismatch_Fails() { Action action = () => Assert.IsInstanceOfType(5); action.Should().Throw() - .WithMessage("Assert.IsInstanceOfType failed. 'value' expression: '5'. Expected type:. Actual type:."); + .WithMessage( + """ + Assertion failed. Expected value to be of type String (or derived). + + expected type: System.String (or derived) + actual type: System.Int32 + + Assert.IsInstanceOfType(5) + """); } public void IsInstanceOfTypeUsingGenericTypeWithOutParameter_WhenValueIsNull_Fails() @@ -224,6 +309,85 @@ public void IsNotInstanceOfType_WhenNonNullNullableTypeAndMessage_LearnNonNull() _ = intType.ToString(); // no warning about possible null } + public void IsNotInstanceOfType_OnExactType_ShouldFailWithActualTypeEvidence() + { + Action action = () => Assert.IsNotInstanceOfType(5, typeof(int)); + action.Should().Throw() + .WithMessage( + """ + Assertion failed. Expected value to not be of type Int32 (or derived). + + not expected type: System.Int32 (or derived) + actual type: System.Int32 + + Assert.IsNotInstanceOfType(5) + """); + } + + public void IsNotInstanceOfType_OnDerivedType_ShouldFailWithActualTypeEvidence() + { + object x = new MemoryStream(); + Action action = () => Assert.IsNotInstanceOfType(x, typeof(Stream)); + action.Should().Throw() + .WithMessage( + """ + Assertion failed. Expected value to not be of type Stream (or derived). + + not expected type: System.IO.Stream (or derived) + actual type: System.IO.MemoryStream + + Assert.IsNotInstanceOfType(x) + """); + } + + public void IsNotInstanceOfType_WhenTypeIsNull_ShouldFailWithDedicatedMessage() + { + Action action = () => Assert.IsNotInstanceOfType(5, null); + action.Should().Throw() + .WithMessage( + """ + Assertion failed. Cannot check type because the not-expected type argument is null. + + Assert.IsNotInstanceOfType(5) + """); + } + + public void IsInstanceOfType_OnFailure_ShouldPopulateExpectedAndActualPayload() + { + try + { + Assert.IsInstanceOfType(5, typeof(string)); + } + catch (AssertFailedException ex) + { + ex.ExpectedText.Should().Be("System.String (or derived)"); + ex.ActualText.Should().Be("System.Int32"); + ex.Data["assert.expected"].Should().Be("System.String (or derived)"); + ex.Data["assert.actual"].Should().Be("System.Int32"); + return; + } + + throw new AssertFailedException("Expected AssertFailedException was not thrown."); + } + + public void IsNotInstanceOfType_OnFailure_ShouldPopulateExpectedAndActualPayload() + { + try + { + Assert.IsNotInstanceOfType(5, typeof(int)); + } + catch (AssertFailedException ex) + { + ex.ExpectedText.Should().Be("System.Int32 (or derived)"); + ex.ActualText.Should().Be("System.Int32"); + ex.Data["assert.expected"].Should().Be("System.Int32 (or derived)"); + ex.Data["assert.actual"].Should().Be("System.Int32"); + return; + } + + throw new AssertFailedException("Expected AssertFailedException was not thrown."); + } + private object? GetObj() => new(); private Type? GetObjType() => typeof(object); diff --git a/test/UnitTests/TestFramework.UnitTests/Assertions/AssertTests.ScopeTests.cs b/test/UnitTests/TestFramework.UnitTests/Assertions/AssertTests.ScopeTests.cs index e78cd8e747..17827b2266 100644 --- a/test/UnitTests/TestFramework.UnitTests/Assertions/AssertTests.ScopeTests.cs +++ b/test/UnitTests/TestFramework.UnitTests/Assertions/AssertTests.ScopeTests.cs @@ -174,7 +174,15 @@ public void Scope_AssertIsInstanceOfType_IsSoftFailure() .Which; innerException.InnerExceptions.Should().HaveCount(2); - innerException.InnerExceptions[0].Message.Should().Be("Assert.IsInstanceOfType failed. 'value' expression: 'value'. Expected type:. Actual type:."); + innerException.InnerExceptions[0].Message.Should().Be( + """ + Assertion failed. Expected value to be of type Int32 (or derived). + + expected type: System.Int32 (or derived) + actual type: System.String + + Assert.IsInstanceOfType(value) + """); innerException.InnerExceptions[1].Message.Should().Contain("Assert.AreEqual failed."); } @@ -197,7 +205,15 @@ public void Scope_AssertIsExactInstanceOfType_IsSoftFailure() .Which; innerException.InnerExceptions.Should().HaveCount(2); - innerException.InnerExceptions[0].Message.Should().Be("Assert.IsExactInstanceOfType failed. 'value' expression: 'value'. Expected exact type:. Actual type:."); + innerException.InnerExceptions[0].Message.Should().Be( + """ + Assertion failed. Expected value to be exactly of type Object. + + expected type: System.Object + actual type: System.String + + Assert.IsExactInstanceOfType(value) + """); innerException.InnerExceptions[1].Message.Should().Contain("Assert.AreEqual failed."); } diff --git a/test/UnitTests/TestFramework.UnitTests/Assertions/AssertionValueRendererTests.cs b/test/UnitTests/TestFramework.UnitTests/Assertions/AssertionValueRendererTests.cs index 20cbe332aa..bdbc362377 100644 --- a/test/UnitTests/TestFramework.UnitTests/Assertions/AssertionValueRendererTests.cs +++ b/test/UnitTests/TestFramework.UnitTests/Assertions/AssertionValueRendererTests.cs @@ -88,6 +88,10 @@ public void RenderValue_Char_ReturnsSingleQuoted() => public void RenderValue_CharNewline_ReturnsEscaped() => AssertionValueRenderer.RenderValue('\n').Should().Be("'\\n'"); + public void RenderValue_ObjectWhoseToStringThrows_ReturnsTypeAndExceptionName() => + AssertionValueRenderer.RenderValue(new ObjectWithThrowingToString()).Should().Be( + "Microsoft.VisualStudio.TestPlatform.TestFramework.UnitTests.AssertionValueRendererTests+ObjectWithThrowingToString (ToString threw InvalidOperationException)"); + private sealed class ObjectWithCustomToString { private readonly string _value; @@ -99,4 +103,9 @@ public ObjectWithCustomToString(string value) public override string ToString() => _value; } + + private sealed class ObjectWithThrowingToString + { + public override string ToString() => throw new InvalidOperationException("boom"); + } }