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();
+ }
+ }
+ }
+}