diff --git a/Source/aweXpect/That/Delegates/ThatDelegateThrows.WithMessage.cs b/Source/aweXpect/That/Delegates/ThatDelegateThrows.WithMessage.cs index 05b5a5933..28d898c22 100644 --- a/Source/aweXpect/That/Delegates/ThatDelegateThrows.WithMessage.cs +++ b/Source/aweXpect/That/Delegates/ThatDelegateThrows.WithMessage.cs @@ -9,7 +9,7 @@ namespace aweXpect; public static partial class ThatDelegateThrows { /// - /// Verifies that the thrown exception has a message equal to + /// Verifies that the thrown exception has a message equal to . /// public static StringEqualityTypeResult> WithMessage( this ThatDelegateThrows source, diff --git a/Source/aweXpect/That/Delegates/ThatDelegateThrows.WithMessageContaining.cs b/Source/aweXpect/That/Delegates/ThatDelegateThrows.WithMessageContaining.cs new file mode 100644 index 000000000..c1dffa7de --- /dev/null +++ b/Source/aweXpect/That/Delegates/ThatDelegateThrows.WithMessageContaining.cs @@ -0,0 +1,31 @@ +using System; +using aweXpect.Core; +using aweXpect.Delegates; +using aweXpect.Options; +using aweXpect.Results; + +namespace aweXpect; + +public static partial class ThatDelegateThrows +{ + /// + /// Verifies that the thrown exception has a message that contains the pattern. + /// + public static StringEqualityResult> WithMessageContaining( + this ThatDelegateThrows source, + string? expected) + where TException : Exception? + { + StringEqualityOptions options = new(); + return new StringEqualityResult>( + source.ExpectationBuilder.AddConstraint((it, grammars) + => new ThatException.HasMessageContainingConstraint( + source.ExpectationBuilder, + it, + grammars | ExpectationGrammars.Active | ExpectationGrammars.Nested, + expected, + options)), + source, + options); + } +} diff --git a/Source/aweXpect/That/Exceptions/ThatException.HasMessage.cs b/Source/aweXpect/That/Exceptions/ThatException.HasMessage.cs index 477f533e1..45069bf19 100644 --- a/Source/aweXpect/That/Exceptions/ThatException.HasMessage.cs +++ b/Source/aweXpect/That/Exceptions/ThatException.HasMessage.cs @@ -10,7 +10,7 @@ namespace aweXpect; public static partial class ThatException { /// - /// Verifies that the actual exception has a message equal to + /// Verifies that the actual exception has a message equal to . /// public static StringEqualityTypeResult> HasMessage( this IThat source, @@ -71,24 +71,7 @@ protected override void AppendNormalResult(StringBuilder stringBuilder, string? => stringBuilder.Append(options.GetExtendedFailure(it, Grammars, Actual?.Message, expected)); protected override void AppendNegatedExpectation(StringBuilder stringBuilder, string? indentation = null) - { - ExpectationGrammars equalityGrammars = Grammars; - if (Grammars.HasFlag(ExpectationGrammars.Active)) - { - stringBuilder.Append("with Message "); - equalityGrammars &= ~ExpectationGrammars.Active; - } - else if (Grammars.HasFlag(ExpectationGrammars.Nested)) - { - stringBuilder.Append("Message is "); - } - else - { - stringBuilder.Append("has Message "); - } - - stringBuilder.Append(options.GetExpectation(expected, equalityGrammars)); - } + => AppendNormalExpectation(stringBuilder, indentation); protected override void AppendNegatedResult(StringBuilder stringBuilder, string? indentation = null) => AppendNormalResult(stringBuilder, indentation); diff --git a/Source/aweXpect/That/Exceptions/ThatException.HasMessageContaining.cs b/Source/aweXpect/That/Exceptions/ThatException.HasMessageContaining.cs new file mode 100644 index 000000000..32451adae --- /dev/null +++ b/Source/aweXpect/That/Exceptions/ThatException.HasMessageContaining.cs @@ -0,0 +1,99 @@ +using System; +using aweXpect.Core; +using aweXpect.Core.Constraints; +using aweXpect.Helpers; +using aweXpect.Options; +using aweXpect.Results; + +namespace aweXpect; + +public static partial class ThatException +{ + /// + /// Verifies that the actual exception has a message containing the pattern. + /// + public static StringEqualityTypeResult> HasMessageContaining( + this IThat source, + string? expected) + { + StringEqualityOptions options = new(); + return new StringEqualityTypeResult>( + source.Get().ExpectationBuilder.AddConstraint((expectationBuilder, it, grammars) + => new HasMessageContainingConstraint( + expectationBuilder, it, grammars, expected, options)), + source, + options); + } + + internal class HasMessageContainingConstraint( + ExpectationBuilder expectationBuilder, + string it, + ExpectationGrammars grammars, + string? expected, + StringEqualityOptions options) + : ConstraintResult.WithValue(grammars), + IValueConstraint + { + public ConstraintResult IsMetBy(Exception? actual) + { + Actual = actual; + options.AsWildcard(); + Outcome = expected is null || options.AreConsideredEqual(actual?.Message, $"*{expected}*") + ? Outcome.Success + : Outcome.Failure; + if (Outcome == Outcome.Failure) + { + expectationBuilder.UpdateContexts(contexts => contexts + .Add(new ResultContext("Message", actual?.Message))); + } + + return this; + } + + protected override void AppendNormalExpectation(StringBuilder stringBuilder, string? indentation = null) + { + ExpectationGrammars equalityGrammars = Grammars; + if (Grammars.HasFlag(ExpectationGrammars.Active)) + { + stringBuilder.Append("with Message containing "); + equalityGrammars &= ~ExpectationGrammars.Active; + } + else if (Grammars.HasFlag(ExpectationGrammars.Nested)) + { + stringBuilder.Append("Message contains "); + } + else + { + stringBuilder.Append("contains Message "); + } + + stringBuilder.Append(options.GetExpectation(expected, equalityGrammars)); + } + + protected override void AppendNormalResult(StringBuilder stringBuilder, string? indentation = null) + => stringBuilder.Append(options.GetExtendedFailure(it, Grammars, Actual?.Message, expected)); + + protected override void AppendNegatedExpectation(StringBuilder stringBuilder, string? indentation = null) + { + ExpectationGrammars equalityGrammars = Grammars; + if (Grammars.HasFlag(ExpectationGrammars.Active)) + { + stringBuilder.Append("with Message not containing "); + equalityGrammars &= ~ExpectationGrammars.Active; + } + else if (Grammars.HasFlag(ExpectationGrammars.Nested)) + { + stringBuilder.Append("Message does not contain "); + } + else + { + stringBuilder.Append("does not contain Message "); + } + + stringBuilder.Append(options.GetExpectation(expected, equalityGrammars.Negate())); + } + + protected override void AppendNegatedResult(StringBuilder stringBuilder, string? indentation = null) + => AppendNormalResult(stringBuilder, indentation); + } +} diff --git a/Tests/aweXpect.Api.Tests/Expected/aweXpect_net8.0.txt b/Tests/aweXpect.Api.Tests/Expected/aweXpect_net8.0.txt index 3ff5ecb7f..30d77f281 100644 --- a/Tests/aweXpect.Api.Tests/Expected/aweXpect_net8.0.txt +++ b/Tests/aweXpect.Api.Tests/Expected/aweXpect_net8.0.txt @@ -280,6 +280,8 @@ namespace aweXpect where TException : System.Exception? { } public static aweXpect.Results.StringEqualityTypeResult> WithMessage(this aweXpect.Delegates.ThatDelegateThrows source, string expected) where TException : System.Exception? { } + public static aweXpect.Results.StringEqualityResult> WithMessageContaining(this aweXpect.Delegates.ThatDelegateThrows source, string? expected) + where TException : System.Exception? { } public static aweXpect.Results.AndOrResult> WithParamName(this aweXpect.Delegates.ThatDelegateThrows source, string? expected) where TException : System.ArgumentException? { } public static aweXpect.Results.AndOrResult> WithRecursiveInnerExceptions(this aweXpect.Delegates.ThatDelegateThrows source, System.Action>> expectations) @@ -669,6 +671,7 @@ namespace aweXpect public static aweXpect.Results.AndOrResult> HasInnerException(this aweXpect.Core.IThat source) { } public static aweXpect.Results.AndOrResult> HasInnerException(this aweXpect.Core.IThat source, System.Action> expectations) { } public static aweXpect.Results.StringEqualityTypeResult> HasMessage(this aweXpect.Core.IThat source, string expected) { } + public static aweXpect.Results.StringEqualityTypeResult> HasMessageContaining(this aweXpect.Core.IThat source, string? expected) { } public static aweXpect.Results.AndOrResult> HasParamName(this aweXpect.Core.IThat source, string? expected) where TException : System.ArgumentException? { } public static aweXpect.Results.AndOrResult> HasRecursiveInnerExceptions(this aweXpect.Core.IThat source, System.Action>> expectations) { } diff --git a/Tests/aweXpect.Api.Tests/Expected/aweXpect_netstandard2.0.txt b/Tests/aweXpect.Api.Tests/Expected/aweXpect_netstandard2.0.txt index e7e99a7e6..5693094e9 100644 --- a/Tests/aweXpect.Api.Tests/Expected/aweXpect_netstandard2.0.txt +++ b/Tests/aweXpect.Api.Tests/Expected/aweXpect_netstandard2.0.txt @@ -131,6 +131,8 @@ namespace aweXpect where TException : System.Exception? { } public static aweXpect.Results.StringEqualityTypeResult> WithMessage(this aweXpect.Delegates.ThatDelegateThrows source, string expected) where TException : System.Exception? { } + public static aweXpect.Results.StringEqualityResult> WithMessageContaining(this aweXpect.Delegates.ThatDelegateThrows source, string? expected) + where TException : System.Exception? { } public static aweXpect.Results.AndOrResult> WithParamName(this aweXpect.Delegates.ThatDelegateThrows source, string? expected) where TException : System.ArgumentException? { } public static aweXpect.Results.AndOrResult> WithRecursiveInnerExceptions(this aweXpect.Delegates.ThatDelegateThrows source, System.Action>> expectations) @@ -454,6 +456,7 @@ namespace aweXpect public static aweXpect.Results.AndOrResult> HasInnerException(this aweXpect.Core.IThat source) { } public static aweXpect.Results.AndOrResult> HasInnerException(this aweXpect.Core.IThat source, System.Action> expectations) { } public static aweXpect.Results.StringEqualityTypeResult> HasMessage(this aweXpect.Core.IThat source, string expected) { } + public static aweXpect.Results.StringEqualityTypeResult> HasMessageContaining(this aweXpect.Core.IThat source, string? expected) { } public static aweXpect.Results.AndOrResult> HasParamName(this aweXpect.Core.IThat source, string? expected) where TException : System.ArgumentException? { } public static aweXpect.Results.AndOrResult> HasRecursiveInnerExceptions(this aweXpect.Core.IThat source, System.Action>> expectations) { } diff --git a/Tests/aweXpect.Tests/Delegates/ThatDelegate.ThrowsException.WithMessage.Tests.cs b/Tests/aweXpect.Tests/Delegates/ThatDelegate.ThrowsException.WithMessage.Tests.cs new file mode 100644 index 000000000..8cb6d1057 --- /dev/null +++ b/Tests/aweXpect.Tests/Delegates/ThatDelegate.ThrowsException.WithMessage.Tests.cs @@ -0,0 +1,135 @@ +namespace aweXpect.Tests; + +public sealed partial class ThatDelegate +{ + public sealed partial class ThrowsException + { + public sealed class WithMessage + { + public sealed class Tests + { + [Fact] + public async Task CanCompareCaseInsensitive() + { + string message = "FOO"; + Exception exception = + new OuterException(message, new CustomException()); + void Delegate() => throw exception; + + async Task Act() + => await That(Delegate).ThrowsException() + .WithMessage("foo").IgnoringCase(); + + await That(Act).DoesNotThrow(); + } + + [Fact] + public async Task CanUseWildcardCheck() + { + string message = "foo-bar"; + Exception exception = + new OuterException(message, new CustomException()); + void Delegate() => throw exception; + + async Task Act() + => await That(Delegate).ThrowsException() + .WithMessage("foo*").AsWildcard(); + + await That(Act).DoesNotThrow(); + } + + [Fact] + public async Task ShouldCompareCaseSensitive() + { + string message = "FOO"; + Exception exception = + new OuterException(message, new CustomException()); + void Delegate() => throw exception; + + async Task Act() + => await That(Delegate).ThrowsException() + .WithMessage("foo"); + + await That(Act).Throws() + .WithMessage(""" + Expected that Delegate + throws an exception with Message equal to "foo", + but it was "FOO" which differs at index 0: + ↓ (actual) + "FOO" + "foo" + ↑ (expected) + + Message: + FOO + """); + } + + [Fact] + public async Task ShouldIncludeExceptionType() + { + string message = "FOO"; + Exception exception = new CustomException(message); + void Delegate() => throw exception; + + async Task Act() + => await That(Delegate).Throws() + .WithMessage("foo"); + + await That(Act).Throws() + .WithMessage(""" + Expected that Delegate + throws a ThatDelegate.CustomException with Message equal to "foo", + but it was "FOO" which differs at index 0: + ↓ (actual) + "FOO" + "foo" + ↑ (expected) + + Message: + FOO + """); + } + + [Theory] + [AutoData] + public async Task WhenAwaited_ShouldReturnThrownException(string message) + { + Exception exception = + new OuterException(message, new CustomException()); + void Delegate() => throw exception; + + Exception result = await That(Delegate) + .ThrowsException().WithMessage(message); + + await That(result).IsSameAs(exception); + } + + [Fact] + public async Task WhenMessagesAreDifferent_ShouldFail() + { + string actual = "actual text"; + string expected = "expected other text"; + Action action = () => throw new CustomException(actual); + + async Task Act() + => await That(action).ThrowsException().WithMessage(expected); + + await That(Act).Throws() + .WithMessage(""" + Expected that action + throws an exception with Message equal to "expected other text", + but it was "actual text" which differs at index 0: + ↓ (actual) + "actual text" + "expected other text" + ↑ (expected) + + Message: + actual text + """); + } + } + } + } +} diff --git a/Tests/aweXpect.Tests/Delegates/ThatDelegate.ThrowsException.WithMessageContaining.Tests.cs b/Tests/aweXpect.Tests/Delegates/ThatDelegate.ThrowsException.WithMessageContaining.Tests.cs new file mode 100644 index 000000000..7d57fcda0 --- /dev/null +++ b/Tests/aweXpect.Tests/Delegates/ThatDelegate.ThrowsException.WithMessageContaining.Tests.cs @@ -0,0 +1,179 @@ +namespace aweXpect.Tests; + +public sealed partial class ThatDelegate +{ + public sealed partial class ThrowsException + { + public sealed class WithMessageContaining + { + public sealed class Tests + { + [Fact] + public async Task CanCompareCaseInsensitive() + { + string message = "_FOO_BAR"; + Exception exception = + new OuterException(message, new CustomException()); + void Delegate() => throw exception; + + async Task Act() + => await That(Delegate).ThrowsException() + .WithMessageContaining("foo").IgnoringCase(); + + await That(Act).DoesNotThrow(); + } + + [Fact] + public async Task CanUseWildcardCheck() + { + string message = "_foo-BAR"; + Exception exception = + new OuterException(message, new CustomException()); + void Delegate() => throw exception; + + async Task Act() + => await That(Delegate).ThrowsException() + .WithMessageContaining("f?o-"); + + await That(Act).DoesNotThrow(); + } + + [Fact] + public async Task ShouldCompareCaseSensitive() + { + string message = "FOO"; + Exception exception = + new OuterException(message, new CustomException()); + void Delegate() => throw exception; + + async Task Act() + => await That(Delegate).ThrowsException() + .WithMessageContaining("foo"); + + await That(Act).Throws() + .WithMessage(""" + Expected that Delegate + throws an exception with Message containing matching "foo", + but it did not match: + ↓ (actual) + "FOO" + "foo" + ↑ (wildcard pattern) + + Message: + FOO + """); + } + + [Fact] + public async Task ShouldIgnorePrecedingText() + { + string message = "some text before foo"; + Exception exception = + new OuterException(message, new CustomException()); + void Delegate() => throw exception; + + async Task Act() + => await That(Delegate).ThrowsException() + .WithMessageContaining("foo"); + + await That(Act).DoesNotThrow(); + } + + [Fact] + public async Task ShouldIgnoreSucceedingText() + { + string message = "foo and some other text"; + Exception exception = + new OuterException(message, new CustomException()); + void Delegate() => throw exception; + + async Task Act() + => await That(Delegate).ThrowsException() + .WithMessageContaining("foo"); + + await That(Act).DoesNotThrow(); + } + + [Fact] + public async Task ShouldIncludeExceptionType() + { + string message = "FOO"; + Exception exception = new CustomException(message); + void Delegate() => throw exception; + + async Task Act() + => await That(Delegate).Throws() + .WithMessageContaining("foo"); + + await That(Act).Throws() + .WithMessage(""" + Expected that Delegate + throws a ThatDelegate.CustomException with Message containing matching "foo", + but it did not match: + ↓ (actual) + "FOO" + "foo" + ↑ (wildcard pattern) + + Message: + FOO + """); + } + + [Theory] + [AutoData] + public async Task WhenAwaited_ShouldReturnThrownException(string message) + { + Exception exception = + new OuterException(message, new CustomException()); + void Delegate() => throw exception; + + Exception result = await That(Delegate) + .ThrowsException().WithMessageContaining(message); + + await That(result).IsSameAs(exception); + } + + [Theory] + [AutoData] + public async Task WhenExpectedIsNull_ShouldSucceed(string message) + { + Exception exception = new(message); + void Delegate() => throw exception; + + async Task Act() + => await That(Delegate).ThrowsException() + .WithMessageContaining(null); + + await That(Act).DoesNotThrow(); + } + + [Fact] + public async Task WhenMessagesAreDifferent_ShouldFail() + { + string actual = "expected actual text"; + string expected = "expected other text"; + Action action = () => throw new CustomException(actual); + + async Task Act() + => await That(action).ThrowsException().WithMessageContaining(expected); + + await That(Act).Throws() + .WithMessage(""" + Expected that action + throws an exception with Message containing matching "expected other text", + but it did not match: + ↓ (actual) + "expected actual text" + "expected other text" + ↑ (wildcard pattern) + + Message: + expected actual text + """); + } + } + } + } +} diff --git a/Tests/aweXpect.Tests/Delegates/ThatDelegate.ThrowsException.WithMessageTests.cs b/Tests/aweXpect.Tests/Delegates/ThatDelegate.ThrowsException.WithMessageTests.cs deleted file mode 100644 index 64657b8f9..000000000 --- a/Tests/aweXpect.Tests/Delegates/ThatDelegate.ThrowsException.WithMessageTests.cs +++ /dev/null @@ -1,132 +0,0 @@ -namespace aweXpect.Tests; - -public sealed partial class ThatDelegate -{ - public sealed partial class ThrowsException - { - public sealed class WithMessageTests - { - [Fact] - public async Task CanCompareCaseInsensitive() - { - string message = "FOO"; - Exception exception = - new OuterException(message, new CustomException()); - void Delegate() => throw exception; - - async Task Act() - => await That(Delegate).ThrowsException() - .WithMessage("foo").IgnoringCase(); - - await That(Act).DoesNotThrow(); - } - - [Fact] - public async Task CanUseWildcardCheck() - { - string message = "foo-bar"; - Exception exception = - new OuterException(message, new CustomException()); - void Delegate() => throw exception; - - async Task Act() - => await That(Delegate).ThrowsException() - .WithMessage("foo*").AsWildcard(); - - await That(Act).DoesNotThrow(); - } - - [Fact] - public async Task ShouldCompareCaseSensitive() - { - string message = "FOO"; - Exception exception = - new OuterException(message, new CustomException()); - void Delegate() => throw exception; - - async Task Act() - => await That(Delegate).ThrowsException() - .WithMessage("foo"); - - await That(Act).Throws() - .WithMessage(""" - Expected that Delegate - throws an exception with Message equal to "foo", - but it was "FOO" which differs at index 0: - ↓ (actual) - "FOO" - "foo" - ↑ (expected) - - Message: - FOO - """); - } - - [Fact] - public async Task ShouldIncludeExceptionType() - { - string message = "FOO"; - Exception exception = new CustomException(message); - void Delegate() => throw exception; - - async Task Act() - => await That(Delegate).Throws() - .WithMessage("foo"); - - await That(Act).Throws() - .WithMessage(""" - Expected that Delegate - throws a ThatDelegate.CustomException with Message equal to "foo", - but it was "FOO" which differs at index 0: - ↓ (actual) - "FOO" - "foo" - ↑ (expected) - - Message: - FOO - """); - } - - [Theory] - [AutoData] - public async Task WhenAwaited_ShouldReturnThrownException(string message) - { - Exception exception = - new OuterException(message, new CustomException()); - void Delegate() => throw exception; - - Exception result = await That(Delegate) - .ThrowsException().WithMessage(message); - - await That(result).IsSameAs(exception); - } - - [Fact] - public async Task WhenMessagesAreDifferent_ShouldFail() - { - string actual = "actual text"; - string expected = "expected other text"; - Action action = () => throw new CustomException(actual); - - async Task Act() - => await That(action).ThrowsException().WithMessage(expected); - - await That(Act).Throws() - .WithMessage(""" - Expected that action - throws an exception with Message equal to "expected other text", - but it was "actual text" which differs at index 0: - ↓ (actual) - "actual text" - "expected other text" - ↑ (expected) - - Message: - actual text - """); - } - } - } -} diff --git a/Tests/aweXpect.Tests/Exceptions/ThatException.HasMessageContaining.Tests.cs b/Tests/aweXpect.Tests/Exceptions/ThatException.HasMessageContaining.Tests.cs new file mode 100644 index 000000000..521132949 --- /dev/null +++ b/Tests/aweXpect.Tests/Exceptions/ThatException.HasMessageContaining.Tests.cs @@ -0,0 +1,176 @@ +namespace aweXpect.Tests; + +public sealed partial class ThatException +{ + public sealed class HasMessageContaining + { + public sealed class Tests + { + [Fact] + public async Task CanCompareCaseInsensitive() + { + string message = "_FOO_BAR"; + MyException exception = new(message); + + async Task Act() + => await That(exception).HasMessageContaining("foo").IgnoringCase(); + + await That(Act).DoesNotThrow(); + } + + [Fact] + public async Task CanUseWildcardCheck() + { + string message = "_foo-BAR"; + MyException exception = new(message); + + async Task Act() + => await That(exception).HasMessageContaining("f?o-"); + + await That(Act).DoesNotThrow(); + } + + [Fact] + public async Task ShouldCompareCaseSensitive() + { + string message = "FOO"; + MyException exception = new(message); + + async Task Act() + => await That(exception).HasMessageContaining("foo"); + + await That(Act).Throws() + .WithMessage(""" + Expected that exception + contains Message matching "foo", + but it did not match: + ↓ (actual) + "FOO" + "foo" + ↑ (wildcard pattern) + + Message: + FOO + """); + } + + [Fact] + public async Task ShouldIgnorePrecedingText() + { + string message = "some text before foo"; + MyException exception = new(message); + + async Task Act() + => await That(exception).HasMessageContaining("foo"); + + await That(Act).DoesNotThrow(); + } + + [Fact] + public async Task ShouldIgnoreSucceedingText() + { + string message = "foo and some other text"; + MyException exception = new(message); + + async Task Act() + => await That(exception).HasMessageContaining("foo"); + + await That(Act).DoesNotThrow(); + } + + [Fact] + public async Task ShouldIncludeExceptionType() + { + string message = "FOO"; + Exception exception = new CustomException(message); + + async Task Act() + => await That(exception).HasMessageContaining("foo"); + + await That(Act).Throws() + .WithMessage(""" + Expected that exception + contains Message matching "foo", + but it did not match: + ↓ (actual) + "FOO" + "foo" + ↑ (wildcard pattern) + + Message: + FOO + """); + } + + [Fact] + public async Task WhenExpectedIsNull_ShouldSucceed() + { + string message = "foo and some other text"; + MyException exception = new(message); + + async Task Act() + => await That(exception).HasMessageContaining(null); + + await That(Act).DoesNotThrow(); + } + + [Fact] + public async Task WhenMessagesAreDifferent_ShouldFail() + { + string actual = "expected actual text"; + string expected = "expected other text"; + CustomException subject = new(actual); + + async Task Act() + => await That(subject).HasMessageContaining(expected); + + await That(Act).Throws() + .WithMessage(""" + Expected that subject + contains Message matching "expected other text", + but it did not match: + ↓ (actual) + "expected actual text" + "expected other text" + ↑ (wildcard pattern) + + Message: + expected actual text + """); + } + } + + public sealed class NegatedTests + { + [Fact] + public async Task WhenStringsAreEqual_ShouldFail() + { + string actual = "my text"; + Exception subject = new(actual); + + async Task Act() + => await That(subject).DoesNotComplyWith(e => e.HasMessageContaining(actual)); + + await That(Act).Throws() + .WithMessage(""" + Expected that subject + does not contain Message matching "my text", + but it was "my text" + """); + } + + [Fact] + public async Task WhenStringsDiffer_ShouldSucceed() + { + string actual = "actual text"; + string expected = "expected other text"; + Exception subject = new(actual); + + async Task Act() + => await That(subject).DoesNotComplyWith(e => e.HasMessageContaining(expected)); + + await That(Act).DoesNotThrow(); + } + } + } +}