From ea3363d4cb799f9f6d4974c13975aabc4dc60d3a Mon Sep 17 00:00:00 2001
From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com>
Date: Thu, 19 Feb 2026 04:00:07 +0000
Subject: [PATCH 1/4] feat: add string length range assertions (HasMinLength,
HasMaxLength, HasLengthBetween)
Add three new string assertion methods for validating string length ranges:
- HasMinLength(int) - asserts string length >= minLength
- HasMaxLength(int) - asserts string length <= maxLength
- HasLengthBetween(int, int) - asserts min <= string length <= max
Closes #4868
---
.../Conditions/StringAssertions.cs | 126 ++++++++++++++++++
.../Extensions/AssertionExtensions.cs | 41 ++++++
2 files changed, 167 insertions(+)
diff --git a/TUnit.Assertions/Conditions/StringAssertions.cs b/TUnit.Assertions/Conditions/StringAssertions.cs
index 8879b37f08..875b89fac7 100644
--- a/TUnit.Assertions/Conditions/StringAssertions.cs
+++ b/TUnit.Assertions/Conditions/StringAssertions.cs
@@ -717,3 +717,129 @@ protected override string GetExpectation()
return "to not match pattern";
}
}
+
+///
+/// Asserts that a string has a length greater than or equal to the specified minimum.
+///
+public class StringMinLengthAssertion : Assertion
+{
+ private readonly int _minLength;
+
+ public StringMinLengthAssertion(
+ AssertionContext context,
+ int minLength)
+ : base(context)
+ {
+ _minLength = minLength;
+ }
+
+ protected override Task CheckAsync(EvaluationMetadata metadata)
+ {
+ var value = metadata.Value;
+ var exception = metadata.Exception;
+
+ if (exception != null)
+ {
+ return Task.FromResult(AssertionResult.Failed($"threw {exception.GetType().Name}"));
+ }
+
+ if (value == null)
+ {
+ return Task.FromResult(AssertionResult.Failed("value was null"));
+ }
+
+ if (value.Length >= _minLength)
+ {
+ return AssertionResult._passedTask;
+ }
+
+ return Task.FromResult(AssertionResult.Failed($"found length {value.Length}"));
+ }
+
+ protected override string GetExpectation() => $"to have a minimum length of {_minLength}";
+}
+
+///
+/// Asserts that a string has a length less than or equal to the specified maximum.
+///
+public class StringMaxLengthAssertion : Assertion
+{
+ private readonly int _maxLength;
+
+ public StringMaxLengthAssertion(
+ AssertionContext context,
+ int maxLength)
+ : base(context)
+ {
+ _maxLength = maxLength;
+ }
+
+ protected override Task CheckAsync(EvaluationMetadata metadata)
+ {
+ var value = metadata.Value;
+ var exception = metadata.Exception;
+
+ if (exception != null)
+ {
+ return Task.FromResult(AssertionResult.Failed($"threw {exception.GetType().Name}"));
+ }
+
+ if (value == null)
+ {
+ return Task.FromResult(AssertionResult.Failed("value was null"));
+ }
+
+ if (value.Length <= _maxLength)
+ {
+ return AssertionResult._passedTask;
+ }
+
+ return Task.FromResult(AssertionResult.Failed($"found length {value.Length}"));
+ }
+
+ protected override string GetExpectation() => $"to have a maximum length of {_maxLength}";
+}
+
+///
+/// Asserts that a string has a length between the specified minimum and maximum (inclusive).
+///
+public class StringLengthBetweenAssertion : Assertion
+{
+ private readonly int _minLength;
+ private readonly int _maxLength;
+
+ public StringLengthBetweenAssertion(
+ AssertionContext context,
+ int minLength,
+ int maxLength)
+ : base(context)
+ {
+ _minLength = minLength;
+ _maxLength = maxLength;
+ }
+
+ protected override Task CheckAsync(EvaluationMetadata metadata)
+ {
+ var value = metadata.Value;
+ var exception = metadata.Exception;
+
+ if (exception != null)
+ {
+ return Task.FromResult(AssertionResult.Failed($"threw {exception.GetType().Name}"));
+ }
+
+ if (value == null)
+ {
+ return Task.FromResult(AssertionResult.Failed("value was null"));
+ }
+
+ if (value.Length >= _minLength && value.Length <= _maxLength)
+ {
+ return AssertionResult._passedTask;
+ }
+
+ return Task.FromResult(AssertionResult.Failed($"found length {value.Length}"));
+ }
+
+ protected override string GetExpectation() => $"to have length between {_minLength} and {_maxLength}";
+}
diff --git a/TUnit.Assertions/Extensions/AssertionExtensions.cs b/TUnit.Assertions/Extensions/AssertionExtensions.cs
index 97b713d664..3cbcc4c612 100644
--- a/TUnit.Assertions/Extensions/AssertionExtensions.cs
+++ b/TUnit.Assertions/Extensions/AssertionExtensions.cs
@@ -723,6 +723,47 @@ public static StringLengthAssertion HasLength(
return new StringLengthAssertion(source.Context, expectedLength);
}
+ ///
+ /// Asserts that the string has a length greater than or equal to the specified minimum.
+ /// Example: await Assert.That(str).HasMinLength(3);
+ ///
+ public static StringMinLengthAssertion HasMinLength(
+ this IAssertionSource source,
+ int minLength,
+ [CallerArgumentExpression(nameof(minLength))] string? expression = null)
+ {
+ source.Context.ExpressionBuilder.Append($".HasMinLength({expression})");
+ return new StringMinLengthAssertion(source.Context, minLength);
+ }
+
+ ///
+ /// Asserts that the string has a length less than or equal to the specified maximum.
+ /// Example: await Assert.That(str).HasMaxLength(10);
+ ///
+ public static StringMaxLengthAssertion HasMaxLength(
+ this IAssertionSource source,
+ int maxLength,
+ [CallerArgumentExpression(nameof(maxLength))] string? expression = null)
+ {
+ source.Context.ExpressionBuilder.Append($".HasMaxLength({expression})");
+ return new StringMaxLengthAssertion(source.Context, maxLength);
+ }
+
+ ///
+ /// Asserts that the string has a length between the specified minimum and maximum (inclusive).
+ /// Example: await Assert.That(str).HasLengthBetween(3, 10);
+ ///
+ public static StringLengthBetweenAssertion HasLengthBetween(
+ this IAssertionSource source,
+ int minLength,
+ int maxLength,
+ [CallerArgumentExpression(nameof(minLength))] string? minExpression = null,
+ [CallerArgumentExpression(nameof(maxLength))] string? maxExpression = null)
+ {
+ source.Context.ExpressionBuilder.Append($".HasLengthBetween({minExpression}, {maxExpression})");
+ return new StringLengthBetweenAssertion(source.Context, minLength, maxLength);
+ }
+
///
/// Asserts that the value is structurally equivalent to the expected value.
/// Performs deep comparison of properties and fields.
From 019eb55aa3f0fea79db5c950712032c6b1332527 Mon Sep 17 00:00:00 2001
From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com>
Date: Mon, 23 Mar 2026 13:41:14 +0000
Subject: [PATCH 2/4] chore: update source generator snapshot files after
rebase
---
...ExceptionAssertions.DotNet8_0.verified.txt | 60 ++++++++++++-------
...ExceptionAssertions.DotNet9_0.verified.txt | 60 ++++++++++++-------
...tesExceptionAssertions.Net4_7.verified.txt | 60 ++++++++++++-------
...tionResultOfTMethod.DotNet8_0.verified.txt | 6 +-
...tionResultOfTMethod.DotNet9_0.verified.txt | 6 +-
...sertionResultOfTMethod.Net4_7.verified.txt | 6 +-
...tionResultOfTMethod.DotNet8_0.verified.txt | 6 +-
...tionResultOfTMethod.DotNet9_0.verified.txt | 6 +-
...sertionResultOfTMethod.Net4_7.verified.txt | 6 +-
9 files changed, 144 insertions(+), 72 deletions(-)
diff --git a/TUnit.Assertions.SourceGenerator.Tests/ExceptionAssertionGeneratorTests.GeneratesExceptionAssertions.DotNet8_0.verified.txt b/TUnit.Assertions.SourceGenerator.Tests/ExceptionAssertionGeneratorTests.GeneratesExceptionAssertions.DotNet8_0.verified.txt
index 776124a663..4c2caae9f8 100644
--- a/TUnit.Assertions.SourceGenerator.Tests/ExceptionAssertionGeneratorTests.GeneratesExceptionAssertions.DotNet8_0.verified.txt
+++ b/TUnit.Assertions.SourceGenerator.Tests/ExceptionAssertionGeneratorTests.GeneratesExceptionAssertions.DotNet8_0.verified.txt
@@ -406,93 +406,113 @@ public static partial class ExceptionAssertionExtensions
///
/// Generated extension method for HasInnerException
///
- public static Exception_HasInnerException_Assertion HasInnerException(this IAssertionSource source)
+ [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")]
+ public static Exception_HasInnerException_Assertion HasInnerException(this IAssertionSource source)
+ where TActual : System.Exception
{
source.Context.ExpressionBuilder.Append(".HasInnerException()");
- return new Exception_HasInnerException_Assertion(source.Context);
+ return new Exception_HasInnerException_Assertion(source.Context.Map(static x => (System.Exception?)x));
}
///
/// Generated extension method for HasNoInnerException
///
- public static Exception_HasNoInnerException_Assertion HasNoInnerException(this IAssertionSource source)
+ [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")]
+ public static Exception_HasNoInnerException_Assertion HasNoInnerException(this IAssertionSource source)
+ where TActual : System.Exception
{
source.Context.ExpressionBuilder.Append(".HasNoInnerException()");
- return new Exception_HasNoInnerException_Assertion(source.Context);
+ return new Exception_HasNoInnerException_Assertion(source.Context.Map(static x => (System.Exception?)x));
}
///
/// Generated extension method for HasStackTrace
///
- public static Exception_HasStackTrace_Assertion HasStackTrace(this IAssertionSource source)
+ [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")]
+ public static Exception_HasStackTrace_Assertion HasStackTrace(this IAssertionSource source)
+ where TActual : System.Exception
{
source.Context.ExpressionBuilder.Append(".HasStackTrace()");
- return new Exception_HasStackTrace_Assertion(source.Context);
+ return new Exception_HasStackTrace_Assertion(source.Context.Map(static x => (System.Exception?)x));
}
///
/// Generated extension method for HasNoData
///
- public static Exception_HasNoData_Assertion HasNoData(this IAssertionSource source)
+ [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")]
+ public static Exception_HasNoData_Assertion HasNoData(this IAssertionSource source)
+ where TActual : System.Exception
{
source.Context.ExpressionBuilder.Append(".HasNoData()");
- return new Exception_HasNoData_Assertion(source.Context);
+ return new Exception_HasNoData_Assertion(source.Context.Map(static x => (System.Exception?)x));
}
///
/// Generated extension method for HasHelpLink
///
- public static Exception_HasHelpLink_Assertion HasHelpLink(this IAssertionSource source)
+ [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")]
+ public static Exception_HasHelpLink_Assertion HasHelpLink(this IAssertionSource source)
+ where TActual : System.Exception
{
source.Context.ExpressionBuilder.Append(".HasHelpLink()");
- return new Exception_HasHelpLink_Assertion(source.Context);
+ return new Exception_HasHelpLink_Assertion(source.Context.Map(static x => (System.Exception?)x));
}
///
/// Generated extension method for HasNoHelpLink
///
- public static Exception_HasNoHelpLink_Assertion HasNoHelpLink(this IAssertionSource source)
+ [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")]
+ public static Exception_HasNoHelpLink_Assertion HasNoHelpLink(this IAssertionSource source)
+ where TActual : System.Exception
{
source.Context.ExpressionBuilder.Append(".HasNoHelpLink()");
- return new Exception_HasNoHelpLink_Assertion(source.Context);
+ return new Exception_HasNoHelpLink_Assertion(source.Context.Map(static x => (System.Exception?)x));
}
///
/// Generated extension method for HasSource
///
- public static Exception_HasSource_Assertion HasSource(this IAssertionSource source)
+ [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")]
+ public static Exception_HasSource_Assertion HasSource(this IAssertionSource source)
+ where TActual : System.Exception
{
source.Context.ExpressionBuilder.Append(".HasSource()");
- return new Exception_HasSource_Assertion(source.Context);
+ return new Exception_HasSource_Assertion(source.Context.Map(static x => (System.Exception?)x));
}
///
/// Generated extension method for HasNoSource
///
- public static Exception_HasNoSource_Assertion HasNoSource(this IAssertionSource source)
+ [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")]
+ public static Exception_HasNoSource_Assertion HasNoSource(this IAssertionSource source)
+ where TActual : System.Exception
{
source.Context.ExpressionBuilder.Append(".HasNoSource()");
- return new Exception_HasNoSource_Assertion(source.Context);
+ return new Exception_HasNoSource_Assertion(source.Context.Map(static x => (System.Exception?)x));
}
///
/// Generated extension method for HasTargetSite
///
+ [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")]
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Exception.TargetSite uses reflection which may be trimmed in AOT scenarios")]
- public static Exception_HasTargetSite_Assertion HasTargetSite(this IAssertionSource source)
+ public static Exception_HasTargetSite_Assertion HasTargetSite(this IAssertionSource source)
+ where TActual : System.Exception
{
source.Context.ExpressionBuilder.Append(".HasTargetSite()");
- return new Exception_HasTargetSite_Assertion(source.Context);
+ return new Exception_HasTargetSite_Assertion(source.Context.Map(static x => (System.Exception?)x));
}
///
/// Generated extension method for HasNoTargetSite
///
+ [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")]
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Exception.TargetSite uses reflection which may be trimmed in AOT scenarios")]
- public static Exception_HasNoTargetSite_Assertion HasNoTargetSite(this IAssertionSource source)
+ public static Exception_HasNoTargetSite_Assertion HasNoTargetSite(this IAssertionSource source)
+ where TActual : System.Exception
{
source.Context.ExpressionBuilder.Append(".HasNoTargetSite()");
- return new Exception_HasNoTargetSite_Assertion(source.Context);
+ return new Exception_HasNoTargetSite_Assertion(source.Context.Map(static x => (System.Exception?)x));
}
}
diff --git a/TUnit.Assertions.SourceGenerator.Tests/ExceptionAssertionGeneratorTests.GeneratesExceptionAssertions.DotNet9_0.verified.txt b/TUnit.Assertions.SourceGenerator.Tests/ExceptionAssertionGeneratorTests.GeneratesExceptionAssertions.DotNet9_0.verified.txt
index 776124a663..4c2caae9f8 100644
--- a/TUnit.Assertions.SourceGenerator.Tests/ExceptionAssertionGeneratorTests.GeneratesExceptionAssertions.DotNet9_0.verified.txt
+++ b/TUnit.Assertions.SourceGenerator.Tests/ExceptionAssertionGeneratorTests.GeneratesExceptionAssertions.DotNet9_0.verified.txt
@@ -406,93 +406,113 @@ public static partial class ExceptionAssertionExtensions
///
/// Generated extension method for HasInnerException
///
- public static Exception_HasInnerException_Assertion HasInnerException(this IAssertionSource source)
+ [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")]
+ public static Exception_HasInnerException_Assertion HasInnerException(this IAssertionSource source)
+ where TActual : System.Exception
{
source.Context.ExpressionBuilder.Append(".HasInnerException()");
- return new Exception_HasInnerException_Assertion(source.Context);
+ return new Exception_HasInnerException_Assertion(source.Context.Map(static x => (System.Exception?)x));
}
///
/// Generated extension method for HasNoInnerException
///
- public static Exception_HasNoInnerException_Assertion HasNoInnerException(this IAssertionSource source)
+ [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")]
+ public static Exception_HasNoInnerException_Assertion HasNoInnerException(this IAssertionSource source)
+ where TActual : System.Exception
{
source.Context.ExpressionBuilder.Append(".HasNoInnerException()");
- return new Exception_HasNoInnerException_Assertion(source.Context);
+ return new Exception_HasNoInnerException_Assertion(source.Context.Map(static x => (System.Exception?)x));
}
///
/// Generated extension method for HasStackTrace
///
- public static Exception_HasStackTrace_Assertion HasStackTrace(this IAssertionSource source)
+ [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")]
+ public static Exception_HasStackTrace_Assertion HasStackTrace(this IAssertionSource source)
+ where TActual : System.Exception
{
source.Context.ExpressionBuilder.Append(".HasStackTrace()");
- return new Exception_HasStackTrace_Assertion(source.Context);
+ return new Exception_HasStackTrace_Assertion(source.Context.Map(static x => (System.Exception?)x));
}
///
/// Generated extension method for HasNoData
///
- public static Exception_HasNoData_Assertion HasNoData(this IAssertionSource source)
+ [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")]
+ public static Exception_HasNoData_Assertion HasNoData(this IAssertionSource source)
+ where TActual : System.Exception
{
source.Context.ExpressionBuilder.Append(".HasNoData()");
- return new Exception_HasNoData_Assertion(source.Context);
+ return new Exception_HasNoData_Assertion(source.Context.Map(static x => (System.Exception?)x));
}
///
/// Generated extension method for HasHelpLink
///
- public static Exception_HasHelpLink_Assertion HasHelpLink(this IAssertionSource source)
+ [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")]
+ public static Exception_HasHelpLink_Assertion HasHelpLink(this IAssertionSource source)
+ where TActual : System.Exception
{
source.Context.ExpressionBuilder.Append(".HasHelpLink()");
- return new Exception_HasHelpLink_Assertion(source.Context);
+ return new Exception_HasHelpLink_Assertion(source.Context.Map(static x => (System.Exception?)x));
}
///
/// Generated extension method for HasNoHelpLink
///
- public static Exception_HasNoHelpLink_Assertion HasNoHelpLink(this IAssertionSource source)
+ [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")]
+ public static Exception_HasNoHelpLink_Assertion HasNoHelpLink(this IAssertionSource source)
+ where TActual : System.Exception
{
source.Context.ExpressionBuilder.Append(".HasNoHelpLink()");
- return new Exception_HasNoHelpLink_Assertion(source.Context);
+ return new Exception_HasNoHelpLink_Assertion(source.Context.Map(static x => (System.Exception?)x));
}
///
/// Generated extension method for HasSource
///
- public static Exception_HasSource_Assertion HasSource(this IAssertionSource source)
+ [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")]
+ public static Exception_HasSource_Assertion HasSource(this IAssertionSource source)
+ where TActual : System.Exception
{
source.Context.ExpressionBuilder.Append(".HasSource()");
- return new Exception_HasSource_Assertion(source.Context);
+ return new Exception_HasSource_Assertion(source.Context.Map(static x => (System.Exception?)x));
}
///
/// Generated extension method for HasNoSource
///
- public static Exception_HasNoSource_Assertion HasNoSource(this IAssertionSource source)
+ [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")]
+ public static Exception_HasNoSource_Assertion HasNoSource(this IAssertionSource source)
+ where TActual : System.Exception
{
source.Context.ExpressionBuilder.Append(".HasNoSource()");
- return new Exception_HasNoSource_Assertion(source.Context);
+ return new Exception_HasNoSource_Assertion(source.Context.Map(static x => (System.Exception?)x));
}
///
/// Generated extension method for HasTargetSite
///
+ [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")]
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Exception.TargetSite uses reflection which may be trimmed in AOT scenarios")]
- public static Exception_HasTargetSite_Assertion HasTargetSite(this IAssertionSource source)
+ public static Exception_HasTargetSite_Assertion HasTargetSite(this IAssertionSource source)
+ where TActual : System.Exception
{
source.Context.ExpressionBuilder.Append(".HasTargetSite()");
- return new Exception_HasTargetSite_Assertion(source.Context);
+ return new Exception_HasTargetSite_Assertion(source.Context.Map(static x => (System.Exception?)x));
}
///
/// Generated extension method for HasNoTargetSite
///
+ [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")]
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Exception.TargetSite uses reflection which may be trimmed in AOT scenarios")]
- public static Exception_HasNoTargetSite_Assertion HasNoTargetSite(this IAssertionSource source)
+ public static Exception_HasNoTargetSite_Assertion HasNoTargetSite(this IAssertionSource source)
+ where TActual : System.Exception
{
source.Context.ExpressionBuilder.Append(".HasNoTargetSite()");
- return new Exception_HasNoTargetSite_Assertion(source.Context);
+ return new Exception_HasNoTargetSite_Assertion(source.Context.Map(static x => (System.Exception?)x));
}
}
diff --git a/TUnit.Assertions.SourceGenerator.Tests/ExceptionAssertionGeneratorTests.GeneratesExceptionAssertions.Net4_7.verified.txt b/TUnit.Assertions.SourceGenerator.Tests/ExceptionAssertionGeneratorTests.GeneratesExceptionAssertions.Net4_7.verified.txt
index 1300707a92..a4f7f65aae 100644
--- a/TUnit.Assertions.SourceGenerator.Tests/ExceptionAssertionGeneratorTests.GeneratesExceptionAssertions.Net4_7.verified.txt
+++ b/TUnit.Assertions.SourceGenerator.Tests/ExceptionAssertionGeneratorTests.GeneratesExceptionAssertions.Net4_7.verified.txt
@@ -404,91 +404,111 @@ public static partial class ExceptionAssertionExtensions
///
/// Generated extension method for HasInnerException
///
- public static Exception_HasInnerException_Assertion HasInnerException(this IAssertionSource source)
+ [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")]
+ public static Exception_HasInnerException_Assertion HasInnerException(this IAssertionSource source)
+ where TActual : System.Exception
{
source.Context.ExpressionBuilder.Append(".HasInnerException()");
- return new Exception_HasInnerException_Assertion(source.Context);
+ return new Exception_HasInnerException_Assertion(source.Context.Map(static x => (System.Exception?)x));
}
///
/// Generated extension method for HasNoInnerException
///
- public static Exception_HasNoInnerException_Assertion HasNoInnerException(this IAssertionSource source)
+ [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")]
+ public static Exception_HasNoInnerException_Assertion HasNoInnerException(this IAssertionSource source)
+ where TActual : System.Exception
{
source.Context.ExpressionBuilder.Append(".HasNoInnerException()");
- return new Exception_HasNoInnerException_Assertion(source.Context);
+ return new Exception_HasNoInnerException_Assertion(source.Context.Map(static x => (System.Exception?)x));
}
///
/// Generated extension method for HasStackTrace
///
- public static Exception_HasStackTrace_Assertion HasStackTrace(this IAssertionSource source)
+ [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")]
+ public static Exception_HasStackTrace_Assertion HasStackTrace(this IAssertionSource source)
+ where TActual : System.Exception
{
source.Context.ExpressionBuilder.Append(".HasStackTrace()");
- return new Exception_HasStackTrace_Assertion(source.Context);
+ return new Exception_HasStackTrace_Assertion(source.Context.Map(static x => (System.Exception?)x));
}
///
/// Generated extension method for HasNoData
///
- public static Exception_HasNoData_Assertion HasNoData(this IAssertionSource source)
+ [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")]
+ public static Exception_HasNoData_Assertion HasNoData(this IAssertionSource source)
+ where TActual : System.Exception
{
source.Context.ExpressionBuilder.Append(".HasNoData()");
- return new Exception_HasNoData_Assertion(source.Context);
+ return new Exception_HasNoData_Assertion(source.Context.Map(static x => (System.Exception?)x));
}
///
/// Generated extension method for HasHelpLink
///
- public static Exception_HasHelpLink_Assertion HasHelpLink(this IAssertionSource source)
+ [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")]
+ public static Exception_HasHelpLink_Assertion HasHelpLink(this IAssertionSource source)
+ where TActual : System.Exception
{
source.Context.ExpressionBuilder.Append(".HasHelpLink()");
- return new Exception_HasHelpLink_Assertion(source.Context);
+ return new Exception_HasHelpLink_Assertion(source.Context.Map(static x => (System.Exception?)x));
}
///
/// Generated extension method for HasNoHelpLink
///
- public static Exception_HasNoHelpLink_Assertion HasNoHelpLink(this IAssertionSource source)
+ [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")]
+ public static Exception_HasNoHelpLink_Assertion HasNoHelpLink(this IAssertionSource source)
+ where TActual : System.Exception
{
source.Context.ExpressionBuilder.Append(".HasNoHelpLink()");
- return new Exception_HasNoHelpLink_Assertion(source.Context);
+ return new Exception_HasNoHelpLink_Assertion(source.Context.Map(static x => (System.Exception?)x));
}
///
/// Generated extension method for HasSource
///
- public static Exception_HasSource_Assertion HasSource(this IAssertionSource source)
+ [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")]
+ public static Exception_HasSource_Assertion HasSource(this IAssertionSource source)
+ where TActual : System.Exception
{
source.Context.ExpressionBuilder.Append(".HasSource()");
- return new Exception_HasSource_Assertion(source.Context);
+ return new Exception_HasSource_Assertion(source.Context.Map(static x => (System.Exception?)x));
}
///
/// Generated extension method for HasNoSource
///
- public static Exception_HasNoSource_Assertion HasNoSource(this IAssertionSource source)
+ [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")]
+ public static Exception_HasNoSource_Assertion HasNoSource(this IAssertionSource source)
+ where TActual : System.Exception
{
source.Context.ExpressionBuilder.Append(".HasNoSource()");
- return new Exception_HasNoSource_Assertion(source.Context);
+ return new Exception_HasNoSource_Assertion(source.Context.Map(static x => (System.Exception?)x));
}
///
/// Generated extension method for HasTargetSite
///
- public static Exception_HasTargetSite_Assertion HasTargetSite(this IAssertionSource source)
+ [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")]
+ public static Exception_HasTargetSite_Assertion HasTargetSite(this IAssertionSource source)
+ where TActual : System.Exception
{
source.Context.ExpressionBuilder.Append(".HasTargetSite()");
- return new Exception_HasTargetSite_Assertion(source.Context);
+ return new Exception_HasTargetSite_Assertion(source.Context.Map(static x => (System.Exception?)x));
}
///
/// Generated extension method for HasNoTargetSite
///
- public static Exception_HasNoTargetSite_Assertion HasNoTargetSite(this IAssertionSource source)
+ [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")]
+ public static Exception_HasNoTargetSite_Assertion HasNoTargetSite(this IAssertionSource source)
+ where TActual : System.Exception
{
source.Context.ExpressionBuilder.Append(".HasNoTargetSite()");
- return new Exception_HasNoTargetSite_Assertion(source.Context);
+ return new Exception_HasNoTargetSite_Assertion(source.Context.Map(static x => (System.Exception?)x));
}
}
diff --git a/TUnit.Assertions.SourceGenerator.Tests/MethodAssertionGeneratorTests.AssertionResultOfTMethod.DotNet8_0.verified.txt b/TUnit.Assertions.SourceGenerator.Tests/MethodAssertionGeneratorTests.AssertionResultOfTMethod.DotNet8_0.verified.txt
index 56eb54174c..62c15bf931 100644
--- a/TUnit.Assertions.SourceGenerator.Tests/MethodAssertionGeneratorTests.AssertionResultOfTMethod.DotNet8_0.verified.txt
+++ b/TUnit.Assertions.SourceGenerator.Tests/MethodAssertionGeneratorTests.AssertionResultOfTMethod.DotNet8_0.verified.txt
@@ -70,10 +70,12 @@ public static partial class AssertionResultOfTMethodExtensions
///
/// Generated extension method for ContainsMatch
///
- public static IEnumerableString_ContainsMatch_String_Assertion ContainsMatch(this IAssertionSource> source, string needle, [CallerArgumentExpression(nameof(needle))] string? needleExpression = null)
+ [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")]
+ public static IEnumerableString_ContainsMatch_String_Assertion ContainsMatch(this IAssertionSource source, string needle, [CallerArgumentExpression(nameof(needle))] string? needleExpression = null)
+ where TActual : System.Collections.Generic.IEnumerable
{
source.Context.ExpressionBuilder.Append($".ContainsMatch({needleExpression})");
- return new IEnumerableString_ContainsMatch_String_Assertion(source.Context, needle);
+ return new IEnumerableString_ContainsMatch_String_Assertion(source.Context.Map>(static x => (System.Collections.Generic.IEnumerable?)x), needle);
}
}
diff --git a/TUnit.Assertions.SourceGenerator.Tests/MethodAssertionGeneratorTests.AssertionResultOfTMethod.DotNet9_0.verified.txt b/TUnit.Assertions.SourceGenerator.Tests/MethodAssertionGeneratorTests.AssertionResultOfTMethod.DotNet9_0.verified.txt
index 56eb54174c..62c15bf931 100644
--- a/TUnit.Assertions.SourceGenerator.Tests/MethodAssertionGeneratorTests.AssertionResultOfTMethod.DotNet9_0.verified.txt
+++ b/TUnit.Assertions.SourceGenerator.Tests/MethodAssertionGeneratorTests.AssertionResultOfTMethod.DotNet9_0.verified.txt
@@ -70,10 +70,12 @@ public static partial class AssertionResultOfTMethodExtensions
///
/// Generated extension method for ContainsMatch
///
- public static IEnumerableString_ContainsMatch_String_Assertion ContainsMatch(this IAssertionSource> source, string needle, [CallerArgumentExpression(nameof(needle))] string? needleExpression = null)
+ [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")]
+ public static IEnumerableString_ContainsMatch_String_Assertion ContainsMatch(this IAssertionSource source, string needle, [CallerArgumentExpression(nameof(needle))] string? needleExpression = null)
+ where TActual : System.Collections.Generic.IEnumerable
{
source.Context.ExpressionBuilder.Append($".ContainsMatch({needleExpression})");
- return new IEnumerableString_ContainsMatch_String_Assertion(source.Context, needle);
+ return new IEnumerableString_ContainsMatch_String_Assertion(source.Context.Map>(static x => (System.Collections.Generic.IEnumerable?)x), needle);
}
}
diff --git a/TUnit.Assertions.SourceGenerator.Tests/MethodAssertionGeneratorTests.AssertionResultOfTMethod.Net4_7.verified.txt b/TUnit.Assertions.SourceGenerator.Tests/MethodAssertionGeneratorTests.AssertionResultOfTMethod.Net4_7.verified.txt
index 56eb54174c..62c15bf931 100644
--- a/TUnit.Assertions.SourceGenerator.Tests/MethodAssertionGeneratorTests.AssertionResultOfTMethod.Net4_7.verified.txt
+++ b/TUnit.Assertions.SourceGenerator.Tests/MethodAssertionGeneratorTests.AssertionResultOfTMethod.Net4_7.verified.txt
@@ -70,10 +70,12 @@ public static partial class AssertionResultOfTMethodExtensions
///
/// Generated extension method for ContainsMatch
///
- public static IEnumerableString_ContainsMatch_String_Assertion ContainsMatch(this IAssertionSource> source, string needle, [CallerArgumentExpression(nameof(needle))] string? needleExpression = null)
+ [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")]
+ public static IEnumerableString_ContainsMatch_String_Assertion ContainsMatch(this IAssertionSource source, string needle, [CallerArgumentExpression(nameof(needle))] string? needleExpression = null)
+ where TActual : System.Collections.Generic.IEnumerable
{
source.Context.ExpressionBuilder.Append($".ContainsMatch({needleExpression})");
- return new IEnumerableString_ContainsMatch_String_Assertion(source.Context, needle);
+ return new IEnumerableString_ContainsMatch_String_Assertion(source.Context.Map>(static x => (System.Collections.Generic.IEnumerable?)x), needle);
}
}
diff --git a/TUnit.Assertions.SourceGenerator.Tests/MethodAssertionGeneratorTests.AsyncAssertionResultOfTMethod.DotNet8_0.verified.txt b/TUnit.Assertions.SourceGenerator.Tests/MethodAssertionGeneratorTests.AsyncAssertionResultOfTMethod.DotNet8_0.verified.txt
index 25500147f3..a6a2aa30eb 100644
--- a/TUnit.Assertions.SourceGenerator.Tests/MethodAssertionGeneratorTests.AsyncAssertionResultOfTMethod.DotNet8_0.verified.txt
+++ b/TUnit.Assertions.SourceGenerator.Tests/MethodAssertionGeneratorTests.AsyncAssertionResultOfTMethod.DotNet8_0.verified.txt
@@ -70,10 +70,12 @@ public static partial class AsyncAssertionResultOfTMethodExtensions
///
/// Generated extension method for ContainsMatchAsync
///
- public static IEnumerableString_ContainsMatchAsync_String_Assertion ContainsMatchAsync(this IAssertionSource> source, string needle, [CallerArgumentExpression(nameof(needle))] string? needleExpression = null)
+ [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")]
+ public static IEnumerableString_ContainsMatchAsync_String_Assertion ContainsMatchAsync(this IAssertionSource source, string needle, [CallerArgumentExpression(nameof(needle))] string? needleExpression = null)
+ where TActual : System.Collections.Generic.IEnumerable
{
source.Context.ExpressionBuilder.Append($".ContainsMatchAsync({needleExpression})");
- return new IEnumerableString_ContainsMatchAsync_String_Assertion(source.Context, needle);
+ return new IEnumerableString_ContainsMatchAsync_String_Assertion(source.Context.Map>(static x => (System.Collections.Generic.IEnumerable?)x), needle);
}
}
diff --git a/TUnit.Assertions.SourceGenerator.Tests/MethodAssertionGeneratorTests.AsyncAssertionResultOfTMethod.DotNet9_0.verified.txt b/TUnit.Assertions.SourceGenerator.Tests/MethodAssertionGeneratorTests.AsyncAssertionResultOfTMethod.DotNet9_0.verified.txt
index 25500147f3..a6a2aa30eb 100644
--- a/TUnit.Assertions.SourceGenerator.Tests/MethodAssertionGeneratorTests.AsyncAssertionResultOfTMethod.DotNet9_0.verified.txt
+++ b/TUnit.Assertions.SourceGenerator.Tests/MethodAssertionGeneratorTests.AsyncAssertionResultOfTMethod.DotNet9_0.verified.txt
@@ -70,10 +70,12 @@ public static partial class AsyncAssertionResultOfTMethodExtensions
///
/// Generated extension method for ContainsMatchAsync
///
- public static IEnumerableString_ContainsMatchAsync_String_Assertion ContainsMatchAsync(this IAssertionSource> source, string needle, [CallerArgumentExpression(nameof(needle))] string? needleExpression = null)
+ [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")]
+ public static IEnumerableString_ContainsMatchAsync_String_Assertion ContainsMatchAsync(this IAssertionSource source, string needle, [CallerArgumentExpression(nameof(needle))] string? needleExpression = null)
+ where TActual : System.Collections.Generic.IEnumerable
{
source.Context.ExpressionBuilder.Append($".ContainsMatchAsync({needleExpression})");
- return new IEnumerableString_ContainsMatchAsync_String_Assertion(source.Context, needle);
+ return new IEnumerableString_ContainsMatchAsync_String_Assertion(source.Context.Map>(static x => (System.Collections.Generic.IEnumerable?)x), needle);
}
}
diff --git a/TUnit.Assertions.SourceGenerator.Tests/MethodAssertionGeneratorTests.AsyncAssertionResultOfTMethod.Net4_7.verified.txt b/TUnit.Assertions.SourceGenerator.Tests/MethodAssertionGeneratorTests.AsyncAssertionResultOfTMethod.Net4_7.verified.txt
index 25500147f3..a6a2aa30eb 100644
--- a/TUnit.Assertions.SourceGenerator.Tests/MethodAssertionGeneratorTests.AsyncAssertionResultOfTMethod.Net4_7.verified.txt
+++ b/TUnit.Assertions.SourceGenerator.Tests/MethodAssertionGeneratorTests.AsyncAssertionResultOfTMethod.Net4_7.verified.txt
@@ -70,10 +70,12 @@ public static partial class AsyncAssertionResultOfTMethodExtensions
///
/// Generated extension method for ContainsMatchAsync
///
- public static IEnumerableString_ContainsMatchAsync_String_Assertion ContainsMatchAsync(this IAssertionSource> source, string needle, [CallerArgumentExpression(nameof(needle))] string? needleExpression = null)
+ [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2091", Justification = "Generic type parameter is only used for property access, not instantiation")]
+ public static IEnumerableString_ContainsMatchAsync_String_Assertion ContainsMatchAsync(this IAssertionSource source, string needle, [CallerArgumentExpression(nameof(needle))] string? needleExpression = null)
+ where TActual : System.Collections.Generic.IEnumerable
{
source.Context.ExpressionBuilder.Append($".ContainsMatchAsync({needleExpression})");
- return new IEnumerableString_ContainsMatchAsync_String_Assertion(source.Context, needle);
+ return new IEnumerableString_ContainsMatchAsync_String_Assertion(source.Context.Map>(static x => (System.Collections.Generic.IEnumerable?)x), needle);
}
}
From 105d7008390b642a2967117e67565ddf26f285e7 Mon Sep 17 00:00:00 2001
From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com>
Date: Mon, 23 Mar 2026 13:41:24 +0000
Subject: [PATCH 3/4] refactor: use [GenerateAssertion] for string length
assertions and add bounds guard
Convert HasMinLength, HasMaxLength, HasLengthBetween from manual assertion
classes to [GenerateAssertion] source-generated assertions, reducing ~170
lines of boilerplate. Add ArgumentOutOfRangeException guard when
minLength > maxLength in HasLengthBetween.
---
.../Conditions/StringAssertions.cs | 126 ------------------
.../StringLengthRangeAssertionExtensions.cs | 45 +++++++
.../Extensions/AssertionExtensions.cs | 41 ------
...Has_No_API_Changes.DotNet10_0.verified.txt | 33 +++++
..._Has_No_API_Changes.DotNet8_0.verified.txt | 33 +++++
..._Has_No_API_Changes.DotNet9_0.verified.txt | 33 +++++
...ary_Has_No_API_Changes.Net4_7.verified.txt | 33 +++++
7 files changed, 177 insertions(+), 167 deletions(-)
create mode 100644 TUnit.Assertions/Conditions/StringLengthRangeAssertionExtensions.cs
diff --git a/TUnit.Assertions/Conditions/StringAssertions.cs b/TUnit.Assertions/Conditions/StringAssertions.cs
index 875b89fac7..8879b37f08 100644
--- a/TUnit.Assertions/Conditions/StringAssertions.cs
+++ b/TUnit.Assertions/Conditions/StringAssertions.cs
@@ -717,129 +717,3 @@ protected override string GetExpectation()
return "to not match pattern";
}
}
-
-///
-/// Asserts that a string has a length greater than or equal to the specified minimum.
-///
-public class StringMinLengthAssertion : Assertion
-{
- private readonly int _minLength;
-
- public StringMinLengthAssertion(
- AssertionContext context,
- int minLength)
- : base(context)
- {
- _minLength = minLength;
- }
-
- protected override Task CheckAsync(EvaluationMetadata metadata)
- {
- var value = metadata.Value;
- var exception = metadata.Exception;
-
- if (exception != null)
- {
- return Task.FromResult(AssertionResult.Failed($"threw {exception.GetType().Name}"));
- }
-
- if (value == null)
- {
- return Task.FromResult(AssertionResult.Failed("value was null"));
- }
-
- if (value.Length >= _minLength)
- {
- return AssertionResult._passedTask;
- }
-
- return Task.FromResult(AssertionResult.Failed($"found length {value.Length}"));
- }
-
- protected override string GetExpectation() => $"to have a minimum length of {_minLength}";
-}
-
-///
-/// Asserts that a string has a length less than or equal to the specified maximum.
-///
-public class StringMaxLengthAssertion : Assertion
-{
- private readonly int _maxLength;
-
- public StringMaxLengthAssertion(
- AssertionContext context,
- int maxLength)
- : base(context)
- {
- _maxLength = maxLength;
- }
-
- protected override Task CheckAsync(EvaluationMetadata metadata)
- {
- var value = metadata.Value;
- var exception = metadata.Exception;
-
- if (exception != null)
- {
- return Task.FromResult(AssertionResult.Failed($"threw {exception.GetType().Name}"));
- }
-
- if (value == null)
- {
- return Task.FromResult(AssertionResult.Failed("value was null"));
- }
-
- if (value.Length <= _maxLength)
- {
- return AssertionResult._passedTask;
- }
-
- return Task.FromResult(AssertionResult.Failed($"found length {value.Length}"));
- }
-
- protected override string GetExpectation() => $"to have a maximum length of {_maxLength}";
-}
-
-///
-/// Asserts that a string has a length between the specified minimum and maximum (inclusive).
-///
-public class StringLengthBetweenAssertion : Assertion
-{
- private readonly int _minLength;
- private readonly int _maxLength;
-
- public StringLengthBetweenAssertion(
- AssertionContext context,
- int minLength,
- int maxLength)
- : base(context)
- {
- _minLength = minLength;
- _maxLength = maxLength;
- }
-
- protected override Task CheckAsync(EvaluationMetadata metadata)
- {
- var value = metadata.Value;
- var exception = metadata.Exception;
-
- if (exception != null)
- {
- return Task.FromResult(AssertionResult.Failed($"threw {exception.GetType().Name}"));
- }
-
- if (value == null)
- {
- return Task.FromResult(AssertionResult.Failed("value was null"));
- }
-
- if (value.Length >= _minLength && value.Length <= _maxLength)
- {
- return AssertionResult._passedTask;
- }
-
- return Task.FromResult(AssertionResult.Failed($"found length {value.Length}"));
- }
-
- protected override string GetExpectation() => $"to have length between {_minLength} and {_maxLength}";
-}
diff --git a/TUnit.Assertions/Conditions/StringLengthRangeAssertionExtensions.cs b/TUnit.Assertions/Conditions/StringLengthRangeAssertionExtensions.cs
new file mode 100644
index 0000000000..bf27c18f81
--- /dev/null
+++ b/TUnit.Assertions/Conditions/StringLengthRangeAssertionExtensions.cs
@@ -0,0 +1,45 @@
+using System;
+using System.ComponentModel;
+using TUnit.Assertions.Attributes;
+using TUnit.Assertions.Core;
+
+namespace TUnit.Assertions.Conditions;
+
+///
+/// Source-generated string length range assertions using [GenerateAssertion] attributes.
+///
+public static partial class StringLengthRangeAssertionExtensions
+{
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [GenerateAssertion(ExpectationMessage = "to have a minimum length of {minLength}")]
+ public static AssertionResult HasMinLength(this string value, int minLength)
+ {
+ return value.Length >= minLength
+ ? AssertionResult.Passed
+ : AssertionResult.Failed($"found length {value.Length}");
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [GenerateAssertion(ExpectationMessage = "to have a maximum length of {maxLength}")]
+ public static AssertionResult HasMaxLength(this string value, int maxLength)
+ {
+ return value.Length <= maxLength
+ ? AssertionResult.Passed
+ : AssertionResult.Failed($"found length {value.Length}");
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [GenerateAssertion(ExpectationMessage = "to have length between {minLength} and {maxLength}")]
+ public static AssertionResult HasLengthBetween(this string value, int minLength, int maxLength)
+ {
+ if (minLength > maxLength)
+ {
+ throw new ArgumentOutOfRangeException(nameof(minLength),
+ $"minLength ({minLength}) must be less than or equal to maxLength ({maxLength}).");
+ }
+
+ return value.Length >= minLength && value.Length <= maxLength
+ ? AssertionResult.Passed
+ : AssertionResult.Failed($"found length {value.Length}");
+ }
+}
diff --git a/TUnit.Assertions/Extensions/AssertionExtensions.cs b/TUnit.Assertions/Extensions/AssertionExtensions.cs
index 3cbcc4c612..97b713d664 100644
--- a/TUnit.Assertions/Extensions/AssertionExtensions.cs
+++ b/TUnit.Assertions/Extensions/AssertionExtensions.cs
@@ -723,47 +723,6 @@ public static StringLengthAssertion HasLength(
return new StringLengthAssertion(source.Context, expectedLength);
}
- ///
- /// Asserts that the string has a length greater than or equal to the specified minimum.
- /// Example: await Assert.That(str).HasMinLength(3);
- ///
- public static StringMinLengthAssertion HasMinLength(
- this IAssertionSource source,
- int minLength,
- [CallerArgumentExpression(nameof(minLength))] string? expression = null)
- {
- source.Context.ExpressionBuilder.Append($".HasMinLength({expression})");
- return new StringMinLengthAssertion(source.Context, minLength);
- }
-
- ///
- /// Asserts that the string has a length less than or equal to the specified maximum.
- /// Example: await Assert.That(str).HasMaxLength(10);
- ///
- public static StringMaxLengthAssertion HasMaxLength(
- this IAssertionSource source,
- int maxLength,
- [CallerArgumentExpression(nameof(maxLength))] string? expression = null)
- {
- source.Context.ExpressionBuilder.Append($".HasMaxLength({expression})");
- return new StringMaxLengthAssertion(source.Context, maxLength);
- }
-
- ///
- /// Asserts that the string has a length between the specified minimum and maximum (inclusive).
- /// Example: await Assert.That(str).HasLengthBetween(3, 10);
- ///
- public static StringLengthBetweenAssertion HasLengthBetween(
- this IAssertionSource source,
- int minLength,
- int maxLength,
- [CallerArgumentExpression(nameof(minLength))] string? minExpression = null,
- [CallerArgumentExpression(nameof(maxLength))] string? maxExpression = null)
- {
- source.Context.ExpressionBuilder.Append($".HasLengthBetween({minExpression}, {maxExpression})");
- return new StringLengthBetweenAssertion(source.Context, minLength, maxLength);
- }
-
///
/// Asserts that the value is structurally equivalent to the expected value.
/// Performs deep comparison of properties and fields.
diff --git a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet10_0.verified.txt b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet10_0.verified.txt
index 3c92472c75..b897d0c5d6 100644
--- a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet10_0.verified.txt
+++ b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet10_0.verified.txt
@@ -2035,6 +2035,15 @@ namespace .Conditions
protected override .<.> CheckAsync(. metadata) { }
protected override string GetExpectation() { }
}
+ public static class StringLengthRangeAssertionExtensions
+ {
+ [.(ExpectationMessage="to have length between {minLength} and {maxLength}")]
+ public static . HasLengthBetween(this string value, int minLength, int maxLength) { }
+ [.(ExpectationMessage="to have a maximum length of {maxLength}")]
+ public static . HasMaxLength(this string value, int maxLength) { }
+ [.(ExpectationMessage="to have a minimum length of {minLength}")]
+ public static . HasMinLength(this string value, int minLength) { }
+ }
public class StringLengthValueAssertion : .
{
public StringLengthValueAssertion(. stringContext) { }
@@ -5128,6 +5137,12 @@ namespace .Extensions
protected override .<.> CheckAsync(. metadata) { }
protected override string GetExpectation() { }
}
+ public static class StringLengthRangeAssertionExtensions
+ {
+ public static ._HasLengthBetween_Int_Int_Assertion HasLengthBetween(this . source, int minLength, int maxLength, [.("minLength")] string? minLengthExpression = null, [.("maxLength")] string? maxLengthExpression = null) { }
+ public static ._HasMaxLength_Int_Assertion HasMaxLength(this . source, int maxLength, [.("maxLength")] string? maxLengthExpression = null) { }
+ public static ._HasMinLength_Int_Assertion HasMinLength(this . source, int minLength, [.("minLength")] string? minLengthExpression = null) { }
+ }
public static class StringStartsWithAssertionExtensions
{
public static . StartsWith(this . source, string expected, [.("expected")] string? expectedExpression = null) { }
@@ -5140,6 +5155,24 @@ namespace .Extensions
public static . IsNullOrEmpty(this . source) { }
public static . IsNullOrWhiteSpace(this . source) { }
}
+ public sealed class String_HasLengthBetween_Int_Int_Assertion : .
+ {
+ public String_HasLengthBetween_Int_Int_Assertion(. context, int minLength, int maxLength) { }
+ protected override .<.> CheckAsync(. metadata) { }
+ protected override string GetExpectation() { }
+ }
+ public sealed class String_HasMaxLength_Int_Assertion : .
+ {
+ public String_HasMaxLength_Int_Assertion(. context, int maxLength) { }
+ protected override .<.> CheckAsync(. metadata) { }
+ protected override string GetExpectation() { }
+ }
+ public sealed class String_HasMinLength_Int_Assertion : .
+ {
+ public String_HasMinLength_Int_Assertion(. context, int minLength) { }
+ protected override .<.> CheckAsync(. metadata) { }
+ protected override string GetExpectation() { }
+ }
public sealed class String_IsEmpty_Assertion : .
{
public String_IsEmpty_Assertion(. context) { }
diff --git a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet8_0.verified.txt b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet8_0.verified.txt
index 2600b47a79..68131a895b 100644
--- a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet8_0.verified.txt
+++ b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet8_0.verified.txt
@@ -2018,6 +2018,15 @@ namespace .Conditions
protected override .<.> CheckAsync(. metadata) { }
protected override string GetExpectation() { }
}
+ public static class StringLengthRangeAssertionExtensions
+ {
+ [.(ExpectationMessage="to have length between {minLength} and {maxLength}")]
+ public static . HasLengthBetween(this string value, int minLength, int maxLength) { }
+ [.(ExpectationMessage="to have a maximum length of {maxLength}")]
+ public static . HasMaxLength(this string value, int maxLength) { }
+ [.(ExpectationMessage="to have a minimum length of {minLength}")]
+ public static . HasMinLength(this string value, int minLength) { }
+ }
public class StringLengthValueAssertion : .
{
public StringLengthValueAssertion(. stringContext) { }
@@ -5068,6 +5077,12 @@ namespace .Extensions
protected override .<.> CheckAsync(. metadata) { }
protected override string GetExpectation() { }
}
+ public static class StringLengthRangeAssertionExtensions
+ {
+ public static ._HasLengthBetween_Int_Int_Assertion HasLengthBetween(this . source, int minLength, int maxLength, [.("minLength")] string? minLengthExpression = null, [.("maxLength")] string? maxLengthExpression = null) { }
+ public static ._HasMaxLength_Int_Assertion HasMaxLength(this . source, int maxLength, [.("maxLength")] string? maxLengthExpression = null) { }
+ public static ._HasMinLength_Int_Assertion HasMinLength(this . source, int minLength, [.("minLength")] string? minLengthExpression = null) { }
+ }
public static class StringStartsWithAssertionExtensions
{
public static . StartsWith(this . source, string expected, [.("expected")] string? expectedExpression = null) { }
@@ -5080,6 +5095,24 @@ namespace .Extensions
public static . IsNullOrEmpty(this . source) { }
public static . IsNullOrWhiteSpace(this . source) { }
}
+ public sealed class String_HasLengthBetween_Int_Int_Assertion : .
+ {
+ public String_HasLengthBetween_Int_Int_Assertion(. context, int minLength, int maxLength) { }
+ protected override .<.> CheckAsync(. metadata) { }
+ protected override string GetExpectation() { }
+ }
+ public sealed class String_HasMaxLength_Int_Assertion : .
+ {
+ public String_HasMaxLength_Int_Assertion(. context, int maxLength) { }
+ protected override .<.> CheckAsync(. metadata) { }
+ protected override string GetExpectation() { }
+ }
+ public sealed class String_HasMinLength_Int_Assertion : .
+ {
+ public String_HasMinLength_Int_Assertion(. context, int minLength) { }
+ protected override .<.> CheckAsync(. metadata) { }
+ protected override string GetExpectation() { }
+ }
public sealed class String_IsEmpty_Assertion : .
{
public String_IsEmpty_Assertion(. context) { }
diff --git a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet9_0.verified.txt b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet9_0.verified.txt
index 87b3a330a5..e896d0349d 100644
--- a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet9_0.verified.txt
+++ b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet9_0.verified.txt
@@ -2035,6 +2035,15 @@ namespace .Conditions
protected override .<.> CheckAsync(. metadata) { }
protected override string GetExpectation() { }
}
+ public static class StringLengthRangeAssertionExtensions
+ {
+ [.(ExpectationMessage="to have length between {minLength} and {maxLength}")]
+ public static . HasLengthBetween(this string value, int minLength, int maxLength) { }
+ [.(ExpectationMessage="to have a maximum length of {maxLength}")]
+ public static . HasMaxLength(this string value, int maxLength) { }
+ [.(ExpectationMessage="to have a minimum length of {minLength}")]
+ public static . HasMinLength(this string value, int minLength) { }
+ }
public class StringLengthValueAssertion : .
{
public StringLengthValueAssertion(. stringContext) { }
@@ -5128,6 +5137,12 @@ namespace .Extensions
protected override .<.> CheckAsync(. metadata) { }
protected override string GetExpectation() { }
}
+ public static class StringLengthRangeAssertionExtensions
+ {
+ public static ._HasLengthBetween_Int_Int_Assertion HasLengthBetween(this . source, int minLength, int maxLength, [.("minLength")] string? minLengthExpression = null, [.("maxLength")] string? maxLengthExpression = null) { }
+ public static ._HasMaxLength_Int_Assertion HasMaxLength(this . source, int maxLength, [.("maxLength")] string? maxLengthExpression = null) { }
+ public static ._HasMinLength_Int_Assertion HasMinLength(this . source, int minLength, [.("minLength")] string? minLengthExpression = null) { }
+ }
public static class StringStartsWithAssertionExtensions
{
public static . StartsWith(this . source, string expected, [.("expected")] string? expectedExpression = null) { }
@@ -5140,6 +5155,24 @@ namespace .Extensions
public static . IsNullOrEmpty(this . source) { }
public static . IsNullOrWhiteSpace(this . source) { }
}
+ public sealed class String_HasLengthBetween_Int_Int_Assertion : .
+ {
+ public String_HasLengthBetween_Int_Int_Assertion(. context, int minLength, int maxLength) { }
+ protected override .<.> CheckAsync(. metadata) { }
+ protected override string GetExpectation() { }
+ }
+ public sealed class String_HasMaxLength_Int_Assertion : .
+ {
+ public String_HasMaxLength_Int_Assertion(. context, int maxLength) { }
+ protected override .<.> CheckAsync(. metadata) { }
+ protected override string GetExpectation() { }
+ }
+ public sealed class String_HasMinLength_Int_Assertion : .
+ {
+ public String_HasMinLength_Int_Assertion(. context, int minLength) { }
+ protected override .<.> CheckAsync(. metadata) { }
+ protected override string GetExpectation() { }
+ }
public sealed class String_IsEmpty_Assertion : .
{
public String_IsEmpty_Assertion(. context) { }
diff --git a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.Net4_7.verified.txt b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.Net4_7.verified.txt
index 1fcf252a29..a88feff098 100644
--- a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.Net4_7.verified.txt
+++ b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.Net4_7.verified.txt
@@ -1811,6 +1811,15 @@ namespace .Conditions
protected override .<.> CheckAsync(. metadata) { }
protected override string GetExpectation() { }
}
+ public static class StringLengthRangeAssertionExtensions
+ {
+ [.(ExpectationMessage="to have length between {minLength} and {maxLength}")]
+ public static . HasLengthBetween(this string value, int minLength, int maxLength) { }
+ [.(ExpectationMessage="to have a maximum length of {maxLength}")]
+ public static . HasMaxLength(this string value, int maxLength) { }
+ [.(ExpectationMessage="to have a minimum length of {minLength}")]
+ public static . HasMinLength(this string value, int minLength) { }
+ }
public class StringLengthValueAssertion : .
{
public StringLengthValueAssertion(. stringContext) { }
@@ -4415,6 +4424,12 @@ namespace .Extensions
protected override .<.> CheckAsync(.