diff --git a/MSTest.slnf b/MSTest.slnf index f8858c6031..b6e4be68c1 100644 --- a/MSTest.slnf +++ b/MSTest.slnf @@ -17,6 +17,7 @@ "src\\Package\\MSTest.Sdk\\MSTest.Sdk.csproj", "src\\Package\\MSTest\\MSTest.csproj", "src\\TestFramework\\TestFramework.Extensions\\TestFramework.Extensions.csproj", + "src\\TestFramework\\TestFramework.SourceGeneration\\TestFramework.SourceGeneration.csproj", "src\\TestFramework\\TestFramework\\TestFramework.csproj", "test\\IntegrationTests\\MSTest.Acceptance.IntegrationTests\\MSTest.Acceptance.IntegrationTests.csproj", "test\\IntegrationTests\\MSTest.IntegrationTests\\MSTest.IntegrationTests.csproj", diff --git a/NonWindowsTests.slnf b/NonWindowsTests.slnf index cc7b8935fd..f7d3a89246 100644 --- a/NonWindowsTests.slnf +++ b/NonWindowsTests.slnf @@ -31,6 +31,7 @@ "src\\Platform\\Microsoft.Testing.Platform.MSBuild\\Microsoft.Testing.Platform.MSBuild.csproj", "src\\Platform\\Microsoft.Testing.Platform\\Microsoft.Testing.Platform.csproj", "src\\TestFramework\\TestFramework.Extensions\\TestFramework.Extensions.csproj", + "src\\TestFramework\\TestFramework.SourceGeneration\\TestFramework.SourceGeneration.csproj", "src\\TestFramework\\TestFramework\\TestFramework.csproj", "test\\IntegrationTests\\MSTest.Acceptance.IntegrationTests\\MSTest.Acceptance.IntegrationTests.csproj", "test\\IntegrationTests\\Microsoft.Testing.Platform.Acceptance.IntegrationTests\\Microsoft.Testing.Platform.Acceptance.IntegrationTests.csproj", diff --git a/src/TestFramework/TestFramework.SourceGeneration/AssertInterpolatedStringAppendMethodsGenerator.cs b/src/TestFramework/TestFramework.SourceGeneration/AssertInterpolatedStringAppendMethodsGenerator.cs index 9c974a001d..e34bd8598a 100644 --- a/src/TestFramework/TestFramework.SourceGeneration/AssertInterpolatedStringAppendMethodsGenerator.cs +++ b/src/TestFramework/TestFramework.SourceGeneration/AssertInterpolatedStringAppendMethodsGenerator.cs @@ -26,7 +26,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) { IncrementalValuesProvider handlers = context.SyntaxProvider.ForAttributeWithMetadataName( MarkerAttributeMetadataName, - predicate: static (node, _) => node is StructDeclarationSyntax, + predicate: static (node, _) => node is StructDeclarationSyntax { Parent: ClassDeclarationSyntax or StructDeclarationSyntax }, transform: static (ctx, _) => GetHandlerInfo(ctx)); context.RegisterSourceOutput(handlers, static (spc, handler) => Emit(spc, handler)); @@ -36,17 +36,8 @@ private static HandlerInfo GetHandlerInfo(GeneratorAttributeSyntaxContext contex { var structSymbol = (INamedTypeSymbol)context.TargetSymbol; - bool nullableLiteralParameter = false; - foreach (AttributeData attribute in context.Attributes) - { - foreach (KeyValuePair namedArgument in attribute.NamedArguments) - { - if (namedArgument.Key == "NullableLiteralParameter" && namedArgument.Value.Value is true) - { - nullableLiteralParameter = true; - } - } - } + bool nullableLiteralParameter = context.Attributes.Any(attribute => attribute.NamedArguments.Any(namedArgument => + namedArgument.Key == "NullableLiteralParameter" && namedArgument.Value.Value is true)); INamedTypeSymbol containingType = structSymbol.ContainingType; string typeParameters = string.Empty; diff --git a/src/TestFramework/TestFramework/Assertions/CollectionAssert.Membership.cs b/src/TestFramework/TestFramework/Assertions/CollectionAssert.Membership.cs index 5203a843eb..7391319704 100644 --- a/src/TestFramework/TestFramework/Assertions/CollectionAssert.Membership.cs +++ b/src/TestFramework/TestFramework/Assertions/CollectionAssert.Membership.cs @@ -62,7 +62,24 @@ public static void Contains([NotNull] ICollection? collection, object? element, } } - Assert.ReportAssertFailed("CollectionAssert.Contains", Assert.BuildUserMessage(message)); + ReportContainsFailed(collection, element, Assert.BuildUserMessage(message)); + } + + [DoesNotReturn] + private static void ReportContainsFailed(ICollection collection, object? element, string? userMessage) + { + string expectedText = AssertionValueRenderer.RenderValue(element); + string collectionText = AssertionValueRenderer.RenderValue(collection); + EvidenceBlock evidence = EvidenceBlock.Create() + .AddLine("expected:", expectedText) + .AddLine("collection:", collectionText); + + StructuredAssertionMessage structured = new(FrameworkMessages.ContainsItemFailedSummary); + structured.WithUserMessage(userMessage); + structured.WithEvidence(evidence); + structured.WithExpectedAndActual(expectedText, null); + + Assert.ReportAssertFailed(structured); } /// @@ -111,11 +128,28 @@ public static void DoesNotContain([NotNull] ICollection? collection, object? ele { if (object.Equals(current, element)) { - Assert.ReportAssertFailed("CollectionAssert.DoesNotContain", Assert.BuildUserMessage(message)); + ReportDoesNotContainFailed(collection, element, Assert.BuildUserMessage(message)); } } } + [DoesNotReturn] + private static void ReportDoesNotContainFailed(ICollection collection, object? element, string? userMessage) + { + string notExpectedText = AssertionValueRenderer.RenderValue(element); + string collectionText = AssertionValueRenderer.RenderValue(collection); + EvidenceBlock evidence = EvidenceBlock.Create() + .AddLine("unexpected:", notExpectedText) + .AddLine("collection:", collectionText); + + StructuredAssertionMessage structured = new(FrameworkMessages.DoesNotContainItemFailedSummary); + structured.WithUserMessage(userMessage); + structured.WithEvidence(evidence); + structured.WithExpectedAndActual(notExpectedText, null); + + Assert.ReportAssertFailed(structured); + } + /// /// Tests whether all items in the specified collection are non-null and throws /// an exception if any element is null. @@ -152,11 +186,26 @@ public static void AllItemsAreNotNull([NotNull] ICollection? collection, string? { if (current == null) { - Assert.ReportAssertFailed("CollectionAssert.AllItemsAreNotNull", Assert.BuildUserMessage(message)); + ReportAllItemsAreNotNullFailed(collection, Assert.BuildUserMessage(message)); } } } + [DoesNotReturn] + private static void ReportAllItemsAreNotNullFailed(ICollection collection, string? userMessage) + { + string collectionText = AssertionValueRenderer.RenderValue(collection); + EvidenceBlock evidence = EvidenceBlock.Create() + .AddLine("collection:", collectionText); + + StructuredAssertionMessage structured = new(FrameworkMessages.AreAllNotNullFailedSummary); + structured.WithUserMessage(userMessage); + structured.WithEvidence(evidence); + structured.WithExpectedAndActual(expectedText: null, actualText: collectionText); + + Assert.ReportAssertFailed(structured); + } + /// /// Tests whether all items in the specified collection are unique or not and /// throws if any two elements in the collection are equal. @@ -193,8 +242,6 @@ public static void AllItemsAreUnique([NotNull] ICollection? collection, string? Assert.CheckParameterNotNull(collection, "CollectionAssert.AllItemsAreUnique", "collection"); - message = Assert.ReplaceNulls(message); - bool foundNull = false; HashSet table = []; foreach (object? current in collection) @@ -208,32 +255,35 @@ public static void AllItemsAreUnique([NotNull] ICollection? collection, string? else { // Found a second occurrence of null. - string userMessage = Assert.BuildUserMessage(message); - string finalMessage = string.Format( - CultureInfo.CurrentCulture, - FrameworkMessages.AllItemsAreUniqueFailMsg, - userMessage, - FrameworkMessages.Common_NullInMessages); - - Assert.ReportAssertFailed("CollectionAssert.AllItemsAreUnique", finalMessage); + ReportAllItemsAreUniqueFailed(collection, duplicate: null, Assert.BuildUserMessage(message)); } } else { if (!table.Add(current)) { - string userMessage = Assert.BuildUserMessage(message); - string finalMessage = string.Format( - CultureInfo.CurrentCulture, - FrameworkMessages.AllItemsAreUniqueFailMsg, - userMessage, - Assert.ReplaceNulls(current)); - - Assert.ReportAssertFailed("CollectionAssert.AllItemsAreUnique", finalMessage); + ReportAllItemsAreUniqueFailed(collection, current, Assert.BuildUserMessage(message)); } } } } + [DoesNotReturn] + private static void ReportAllItemsAreUniqueFailed(ICollection collection, object? duplicate, string? userMessage) + { + string duplicateText = AssertionValueRenderer.RenderValue(duplicate); + string collectionText = AssertionValueRenderer.RenderValue(collection); + EvidenceBlock evidence = EvidenceBlock.Create() + .AddLine("duplicate:", duplicateText) + .AddLine("collection:", collectionText); + + StructuredAssertionMessage structured = new(FrameworkMessages.AreAllDistinctFailedSummary); + structured.WithUserMessage(userMessage); + structured.WithEvidence(evidence); + structured.WithExpectedAndActual(expectedText: null, actualText: collectionText); + + Assert.ReportAssertFailed(structured); + } + #endregion } diff --git a/src/TestFramework/TestFramework/Assertions/StringAssert.cs b/src/TestFramework/TestFramework/Assertions/StringAssert.cs index f7ab02bc93..f3eee63d17 100644 --- a/src/TestFramework/TestFramework/Assertions/StringAssert.cs +++ b/src/TestFramework/TestFramework/Assertions/StringAssert.cs @@ -160,12 +160,28 @@ public static void Contains([NotNull] string? value, [NotNull] string? substring Assert.CheckParameterNotNull(substring, "StringAssert.Contains", "substring"); if (value.IndexOf(substring, comparisonType) < 0) { - string userMessage = Assert.BuildUserMessage(message); - string finalMessage = string.Format(CultureInfo.CurrentCulture, FrameworkMessages.ContainsFail, value, substring, userMessage); - Assert.ReportAssertFailed("StringAssert.Contains", finalMessage); + ReportContainsFailed(value, substring, comparisonType, Assert.BuildUserMessage(message)); } } + [DoesNotReturn] + private static void ReportContainsFailed(string value, string substring, StringComparison comparisonType, string? userMessage) + { + string expectedText = AssertionValueRenderer.RenderValue(substring); + string actualText = AssertionValueRenderer.RenderValue(value); + EvidenceBlock evidence = EvidenceBlock.Create() + .AddLine("expected substring:", expectedText) + .AddLine("actual:", actualText) + .AddLine("comparison:", comparisonType.ToString()); + + StructuredAssertionMessage structured = new(FrameworkMessages.ContainsSubstringFailedSummary); + structured.WithUserMessage(userMessage); + structured.WithEvidence(evidence); + structured.WithExpectedAndActual(expectedText, actualText); + + Assert.ReportAssertFailed(structured); + } + /// /// Tests whether the specified string begins with the specified substring /// and throws an exception if the test string does not start with the @@ -259,12 +275,28 @@ public static void StartsWith([NotNull] string? value, [NotNull] string? substri Assert.CheckParameterNotNull(substring, "StringAssert.StartsWith", "substring"); if (!value.StartsWith(substring, comparisonType)) { - string userMessage = Assert.BuildUserMessage(message); - string finalMessage = string.Format(CultureInfo.CurrentCulture, FrameworkMessages.StartsWithFail, value, substring, userMessage); - Assert.ReportAssertFailed("StringAssert.StartsWith", finalMessage); + ReportStartsWithFailed(value, substring, comparisonType, Assert.BuildUserMessage(message)); } } + [DoesNotReturn] + private static void ReportStartsWithFailed(string value, string substring, StringComparison comparisonType, string? userMessage) + { + string expectedText = AssertionValueRenderer.RenderValue(substring); + string actualText = AssertionValueRenderer.RenderValue(value); + EvidenceBlock evidence = EvidenceBlock.Create() + .AddLine("expected prefix:", expectedText) + .AddLine("actual:", actualText) + .AddLine("comparison:", comparisonType.ToString()); + + StructuredAssertionMessage structured = new(FrameworkMessages.StartsWithFailedSummary); + structured.WithUserMessage(userMessage); + structured.WithEvidence(evidence); + structured.WithExpectedAndActual(expectedText, actualText); + + Assert.ReportAssertFailed(structured); + } + /// /// Tests whether the specified string ends with the specified substring /// and throws an exception if the test string does not end with the @@ -358,12 +390,28 @@ public static void EndsWith([NotNull] string? value, [NotNull] string? substring Assert.CheckParameterNotNull(substring, "StringAssert.EndsWith", "substring"); if (!value.EndsWith(substring, comparisonType)) { - string userMessage = Assert.BuildUserMessage(message); - string finalMessage = string.Format(CultureInfo.CurrentCulture, FrameworkMessages.EndsWithFail, value, substring, userMessage); - Assert.ReportAssertFailed("StringAssert.EndsWith", finalMessage); + ReportEndsWithFailed(value, substring, comparisonType, Assert.BuildUserMessage(message)); } } + [DoesNotReturn] + private static void ReportEndsWithFailed(string value, string substring, StringComparison comparisonType, string? userMessage) + { + string expectedText = AssertionValueRenderer.RenderValue(substring); + string actualText = AssertionValueRenderer.RenderValue(value); + EvidenceBlock evidence = EvidenceBlock.Create() + .AddLine("expected suffix:", expectedText) + .AddLine("actual:", actualText) + .AddLine("comparison:", comparisonType.ToString()); + + StructuredAssertionMessage structured = new(FrameworkMessages.EndsWithFailedSummary); + structured.WithUserMessage(userMessage); + structured.WithEvidence(evidence); + structured.WithExpectedAndActual(expectedText, actualText); + + Assert.ReportAssertFailed(structured); + } + #endregion Substrings #region Regular Expressions @@ -415,12 +463,27 @@ public static void Matches([NotNull] string? value, [NotNull] Regex? pattern, st if (!pattern.IsMatch(value)) { - string userMessage = Assert.BuildUserMessage(message); - string finalMessage = string.Format(CultureInfo.CurrentCulture, FrameworkMessages.IsMatchFail, value, pattern, userMessage); - Assert.ReportAssertFailed("StringAssert.Matches", finalMessage); + ReportMatchesFailed(value, pattern, Assert.BuildUserMessage(message)); } } + [DoesNotReturn] + private static void ReportMatchesFailed(string value, Regex pattern, string? userMessage) + { + string patternText = AssertionValueRenderer.RenderValue(pattern.ToString()); + string actualText = AssertionValueRenderer.RenderValue(value); + EvidenceBlock evidence = EvidenceBlock.Create() + .AddLine("expected pattern:", patternText) + .AddLine("actual:", actualText); + + StructuredAssertionMessage structured = new(FrameworkMessages.MatchesRegexFailedSummary); + structured.WithUserMessage(userMessage); + structured.WithEvidence(evidence); + structured.WithExpectedAndActual(patternText, actualText); + + Assert.ReportAssertFailed(structured); + } + /// /// Tests whether the specified string does not match a regular expression /// and throws an exception if the string matches the expression. @@ -468,12 +531,27 @@ public static void DoesNotMatch([NotNull] string? value, [NotNull] Regex? patter if (pattern.IsMatch(value)) { - string userMessage = Assert.BuildUserMessage(message); - string finalMessage = string.Format(CultureInfo.CurrentCulture, FrameworkMessages.IsNotMatchFail, value, pattern, userMessage); - Assert.ReportAssertFailed("StringAssert.DoesNotMatch", finalMessage); + ReportDoesNotMatchFailed(value, pattern, Assert.BuildUserMessage(message)); } } + [DoesNotReturn] + private static void ReportDoesNotMatchFailed(string value, Regex pattern, string? userMessage) + { + string patternText = AssertionValueRenderer.RenderValue(pattern.ToString()); + string actualText = AssertionValueRenderer.RenderValue(value); + EvidenceBlock evidence = EvidenceBlock.Create() + .AddLine("unexpected pattern:", patternText) + .AddLine("actual:", actualText); + + StructuredAssertionMessage structured = new(FrameworkMessages.DoesNotMatchRegexFailedSummary); + structured.WithUserMessage(userMessage); + structured.WithEvidence(evidence); + structured.WithExpectedAndActual(patternText, actualText); + + Assert.ReportAssertFailed(structured); + } + #endregion Regular Expressions #region DoNotUse diff --git a/test/UnitTests/TestFramework.UnitTests/Assertions/CollectionAssertTests.cs b/test/UnitTests/TestFramework.UnitTests/Assertions/CollectionAssertTests.cs index d431a5c7c7..3aa3ba2e48 100644 --- a/test/UnitTests/TestFramework.UnitTests/Assertions/CollectionAssertTests.cs +++ b/test/UnitTests/TestFramework.UnitTests/Assertions/CollectionAssertTests.cs @@ -75,6 +75,51 @@ public void CollectionAssertAllItemsAreUniqueMessageNullabilityPostConditions() _ = collection.Count; // no warning } + public void CollectionAssertContainsPopulatesStructuredExpectedWithoutActual() + { + ICollection collection = new[] { "a", "b" }; + Action action = () => CollectionAssert.Contains(collection, "c"); + AssertFailedException ex = action.Should().Throw().Which; + ex.Message.Should().Contain("Expected collection to contain the specified element."); + ex.ExpectedText.Should().Be("\"c\""); + ex.Data["assert.expected"].Should().Be("\"c\""); + ex.ActualText.Should().BeNull(); + ex.Data.Contains("assert.actual").Should().BeFalse(); + } + + public void CollectionAssertDoesNotContainPopulatesStructuredExpectedWithoutActual() + { + ICollection collection = new[] { "a", "b" }; + Action action = () => CollectionAssert.DoesNotContain(collection, "a"); + AssertFailedException ex = action.Should().Throw().Which; + ex.Message.Should().Contain("Expected collection to not contain the specified element."); + ex.ExpectedText.Should().Be("\"a\""); + ex.Data["assert.expected"].Should().Be("\"a\""); + ex.ActualText.Should().BeNull(); + ex.Data.Contains("assert.actual").Should().BeFalse(); + } + + public void CollectionAssertAllItemsAreNotNullPopulatesStructuredActual() + { + ICollection collection = new[] { "a", null }; + Action action = () => CollectionAssert.AllItemsAreNotNull(collection); + AssertFailedException ex = action.Should().Throw().Which; + ex.Message.Should().Contain("Expected all items in collection to be non-null."); + ex.ActualText.Should().NotBeNull(); + ex.Data["assert.actual"].Should().NotBeNull(); + } + + public void CollectionAssertAllItemsAreUniquePopulatesStructuredActual() + { + ICollection collection = new[] { "a", "a" }; + Action action = () => CollectionAssert.AllItemsAreUnique(collection); + AssertFailedException ex = action.Should().Throw().Which; + ex.Message.Should().Contain("Expected all items in collection to be distinct."); + ex.Message.Should().Contain("duplicate:"); + ex.ActualText.Should().NotBeNull(); + ex.Data["assert.actual"].Should().NotBeNull(); + } + public void CollectionAssertIsSubsetOfNullabilityPostConditions() { ICollection? collection = GetCollection(); diff --git a/test/UnitTests/TestFramework.UnitTests/Assertions/StringAssertTests.cs b/test/UnitTests/TestFramework.UnitTests/Assertions/StringAssertTests.cs index 09d182fa59..3aef59d318 100644 --- a/test/UnitTests/TestFramework.UnitTests/Assertions/StringAssertTests.cs +++ b/test/UnitTests/TestFramework.UnitTests/Assertions/StringAssertTests.cs @@ -19,7 +19,7 @@ public void StringAssertContains() string notInString = "I'm not in the string above"; Action action = () => StringAssert.Contains(actual, notInString); action.Should().Throw() - .And.Message.Should().Contain("StringAssert.Contains failed"); + .And.Message.Should().Contain("Expected string to contain the specified substring."); } public void StringAssertStartsWith() @@ -28,7 +28,7 @@ public void StringAssertStartsWith() string notInString = "I'm not in the string above"; Action action = () => StringAssert.StartsWith(actual, notInString); action.Should().Throw() - .And.Message.Should().Contain("StringAssert.StartsWith failed"); + .And.Message.Should().Contain("Expected string to start with the specified prefix."); } public void StringAssertEndsWith() @@ -37,7 +37,7 @@ public void StringAssertEndsWith() string notInString = "I'm not in the string above"; Action action = () => StringAssert.EndsWith(actual, notInString); action.Should().Throw() - .And.Message.Should().Contain("StringAssert.EndsWith failed"); + .And.Message.Should().Contain("Expected string to end with the specified suffix."); } public void StringAssertDoesNotMatch() @@ -46,7 +46,62 @@ public void StringAssertDoesNotMatch() Regex doesMatch = new("quick brown fox"); Action action = () => StringAssert.DoesNotMatch(actual, doesMatch); action.Should().Throw() - .And.Message.Should().Contain("StringAssert.DoesNotMatch failed"); + .And.Message.Should().Contain("Expected string to not match the specified pattern."); + } + + public void StringAssertContainsPopulatesStructuredExpectedAndActual() + { + string actual = "The quick brown fox jumps over the lazy dog."; + string notInString = "I'm not in the string above"; + Action action = () => StringAssert.Contains(actual, notInString); + AssertFailedException ex = action.Should().Throw().Which; + ex.ExpectedText.Should().Be("\"I'm not in the string above\""); + ex.ActualText.Should().Be("\"The quick brown fox jumps over the lazy dog.\""); + ex.Data["assert.expected"].Should().Be("\"I'm not in the string above\""); + ex.Data["assert.actual"].Should().Be("\"The quick brown fox jumps over the lazy dog.\""); + ex.Message.Should().MatchRegex(@"comparison:\s+Ordinal"); + } + + public void StringAssertStartsWithPopulatesStructuredExpectedAndActual() + { + Action action = () => StringAssert.StartsWith("hello world", "world"); + AssertFailedException ex = action.Should().Throw().Which; + ex.ExpectedText.Should().Be("\"world\""); + ex.ActualText.Should().Be("\"hello world\""); + ex.Data["assert.expected"].Should().Be("\"world\""); + ex.Data["assert.actual"].Should().Be("\"hello world\""); + ex.Message.Should().MatchRegex(@"comparison:\s+Ordinal"); + } + + public void StringAssertEndsWithPopulatesStructuredExpectedAndActual() + { + Action action = () => StringAssert.EndsWith("hello world", "hello"); + AssertFailedException ex = action.Should().Throw().Which; + ex.ExpectedText.Should().Be("\"hello\""); + ex.ActualText.Should().Be("\"hello world\""); + ex.Data["assert.expected"].Should().Be("\"hello\""); + ex.Data["assert.actual"].Should().Be("\"hello world\""); + ex.Message.Should().MatchRegex(@"comparison:\s+Ordinal"); + } + + public void StringAssertMatchesPopulatesStructuredExpectedAndActual() + { + Action action = () => StringAssert.Matches("hello", new Regex("world")); + AssertFailedException ex = action.Should().Throw().Which; + ex.ExpectedText.Should().Be("\"world\""); + ex.ActualText.Should().Be("\"hello\""); + ex.Data["assert.expected"].Should().Be("\"world\""); + ex.Data["assert.actual"].Should().Be("\"hello\""); + } + + public void StringAssertDoesNotMatchPopulatesStructuredExpectedAndActual() + { + Action action = () => StringAssert.DoesNotMatch("hello", new Regex("hello")); + AssertFailedException ex = action.Should().Throw().Which; + ex.ExpectedText.Should().Be("\"hello\""); + ex.ActualText.Should().Be("\"hello\""); + ex.Data["assert.expected"].Should().Be("\"hello\""); + ex.Data["assert.actual"].Should().Be("\"hello\""); } public void StringAssertContainsIgnoreCase_DoesNotThrow() @@ -75,7 +130,7 @@ public void StringAssertContainsDoesNotThrowFormatException() { Action action = () => StringAssert.Contains(":-{", "x"); action.Should().Throw() - .And.Message.Should().Contain("StringAssert.Contains failed"); + .And.Message.Should().Contain("Expected string to contain the specified substring."); } // See https://github.com/dotnet/sdk/issues/25373 @@ -83,7 +138,7 @@ public void StringAssertContainsDoesNotThrowFormatExceptionWithArguments() { Action action = () => StringAssert.Contains("{", "x", "message"); action.Should().Throw() - .And.Message.Should().Contain("StringAssert.Contains failed"); + .And.Message.Should().Contain("Expected string to contain the specified substring."); } public void StringAssertContainsNullabilitiesPostConditions()