diff --git a/TUnit.Assertions.Tests/CollectionAssertionTests.cs b/TUnit.Assertions.Tests/CollectionAssertionTests.cs index 751e68dc5f..ffb9a9b92d 100644 --- a/TUnit.Assertions.Tests/CollectionAssertionTests.cs +++ b/TUnit.Assertions.Tests/CollectionAssertionTests.cs @@ -1,4 +1,3 @@ -using System.Diagnostics.CodeAnalysis; using TUnit.Assertions.Extensions; namespace TUnit.Assertions.Tests; @@ -37,17 +36,6 @@ public async Task Count2() await Assert.That(() => items).Count().IsEqualTo(0); } - [Test] - [SuppressMessage("Obsolete", "CS0618:Type or member is obsolete")] - public async Task Count_WithPredicate() - { - var items = new List { 1, 2, 3, 4, 5 }; - -#pragma warning disable CS0618 // Type or member is obsolete - await Assert.That(items).Count(x => x > 2).IsEqualTo(3); -#pragma warning restore CS0618 - } - [Test] public async Task Count_WithInnerAssertion_IsGreaterThan() { @@ -128,4 +116,158 @@ public async Task Count_WithInnerAssertion_Lambda_Collection() // Test with lambda-wrapped collection await Assert.That(() => items).Count(item => item.IsGreaterThan(2)).IsEqualTo(3); } + + // Tests for collection chaining after Count assertions + + [Test] + public async Task Count_ThenAnd_Contains() + { + var items = new List { 1, 2, 3, 4, 5 }; + + // Count and then chain with Contains + await Assert.That(items) + .Count().IsEqualTo(5) + .And.Contains(3); + } + + [Test] + public async Task Count_ThenAnd_IsNotEmpty() + { + var items = new List { 1, 2, 3, 4, 5 }; + + // Count and then chain with IsNotEmpty + await Assert.That(items) + .Count().IsGreaterThan(0) + .And.IsNotEmpty(); + } + + [Test] + public async Task Count_WithInnerAssertion_ThenAnd_Contains() + { + var items = new List { 1, 2, 3, 4, 5 }; + + // Count with inner assertion and then chain with Contains + await Assert.That(items) + .Count(item => item.IsGreaterThan(2)).IsEqualTo(3) + .And.Contains(5); + } + + [Test] + public async Task Count_ThenAnd_All() + { + var items = new List { 1, 2, 3, 4, 5 }; + + // Count and then chain with All + await Assert.That(items) + .Count().IsEqualTo(5) + .And.All(x => x > 0); + } + + [Test] + public async Task Count_ThenAnd_Count() + { + var items = new List { 1, 2, 3, 4, 5 }; + + // Chain multiple Count assertions + await Assert.That(items) + .Count().IsGreaterThan(3) + .And.Count().IsLessThan(10); + } + + [Test] + public async Task Count_WithInnerAssertion_ThenAnd_IsInOrder() + { + var items = new List { 1, 2, 3, 4, 5 }; + + // Count with inner assertion and then check ordering + await Assert.That(items) + .Count(item => item.IsGreaterThan(0)).IsEqualTo(5) + .And.IsInOrder(); + } + + [Test] + public async Task Count_IsGreaterThan() + { + var items = new List { 1, 2, 3, 4, 5 }; + + await Assert.That(items).Count().IsGreaterThan(3); + } + + [Test] + public async Task Count_IsLessThan() + { + var items = new List { 1, 2, 3 }; + + await Assert.That(items).Count().IsLessThan(5); + } + + [Test] + public async Task Count_IsGreaterThanOrEqualTo() + { + var items = new List { 1, 2, 3, 4, 5 }; + + await Assert.That(items).Count().IsGreaterThanOrEqualTo(5); + } + + [Test] + public async Task Count_IsLessThanOrEqualTo() + { + var items = new List { 1, 2, 3, 4, 5 }; + + await Assert.That(items).Count().IsLessThanOrEqualTo(5); + } + + [Test] + public async Task Count_IsZero() + { + var items = new List(); + + await Assert.That(items).Count().IsZero(); + } + + [Test] + public async Task Count_IsPositive() + { + var items = new List { 1 }; + + await Assert.That(items).Count().IsPositive(); + } + + [Test] + public async Task Count_IsNotEqualTo() + { + var items = new List { 1, 2, 3 }; + + await Assert.That(items).Count().IsNotEqualTo(5); + } + + [Test] + public async Task Chained_Collection_Assertions() + { + var numbers = new[] { 1, 2, 3, 4, 5 }; + + // For collections of int, use Count().IsEqualTo(5) instead of Count(c => c.IsEqualTo(5)) + // to avoid ambiguity with item-filtering + await Assert.That(numbers) + .IsNotEmpty() + .And.Count().IsEqualTo(5) + .And.Contains(3) + .And.DoesNotContain(10) + .And.IsInOrder() + .And.All(n => n > 0) + .And.Any(n => n == 5); + } + + [Test] + public async Task Chained_Collection_Assertions_WithStrings() + { + var names = new[] { "Alice", "Bob", "Charlie" }; + + // For non-int collections, Count(c => c.IsEqualTo(3)) works unambiguously + await Assert.That(names) + .IsNotEmpty() + .And.Count(c => c.IsEqualTo(3)) + .And.Contains("Bob") + .And.DoesNotContain("Dave"); + } } diff --git a/TUnit.Assertions/Conditions/CollectionCountSource.cs b/TUnit.Assertions/Conditions/CollectionCountSource.cs new file mode 100644 index 0000000000..4ffd07fd24 --- /dev/null +++ b/TUnit.Assertions/Conditions/CollectionCountSource.cs @@ -0,0 +1,322 @@ +using System.Runtime.CompilerServices; +using TUnit.Assertions.Core; +using TUnit.Assertions.Sources; + +namespace TUnit.Assertions.Conditions; + +/// +/// Provides count assertions that preserve collection type for further chaining. +/// This enables patterns like: Assert.That(list).Count(item => item.IsGreaterThan(3)).IsEqualTo(2).And.Contains(5) +/// +public class CollectionCountSource + where TCollection : IEnumerable +{ + private readonly AssertionContext _collectionContext; + private readonly Func, Assertion?>? _assertion; + + public CollectionCountSource( + AssertionContext collectionContext, + Func, Assertion?>? assertion) + { + _collectionContext = collectionContext; + _assertion = assertion; + } + + /// + /// Asserts that the count is equal to the expected value. + /// Returns a collection-aware assertion that allows further collection chaining. + /// + public CollectionCountEqualsAssertion IsEqualTo( + int expected, + [CallerArgumentExpression(nameof(expected))] string? expression = null) + { + _collectionContext.ExpressionBuilder.Append($".IsEqualTo({expression})"); + return new CollectionCountEqualsAssertion( + _collectionContext, _assertion, expected, CountComparison.Equal); + } + + /// + /// Asserts that the count is not equal to the expected value. + /// Returns a collection-aware assertion that allows further collection chaining. + /// + public CollectionCountEqualsAssertion IsNotEqualTo( + int expected, + [CallerArgumentExpression(nameof(expected))] string? expression = null) + { + _collectionContext.ExpressionBuilder.Append($".IsNotEqualTo({expression})"); + return new CollectionCountEqualsAssertion( + _collectionContext, _assertion, expected, CountComparison.NotEqual); + } + + /// + /// Asserts that the count is greater than the expected value. + /// Returns a collection-aware assertion that allows further collection chaining. + /// + public CollectionCountEqualsAssertion IsGreaterThan( + int expected, + [CallerArgumentExpression(nameof(expected))] string? expression = null) + { + _collectionContext.ExpressionBuilder.Append($".IsGreaterThan({expression})"); + return new CollectionCountEqualsAssertion( + _collectionContext, _assertion, expected, CountComparison.GreaterThan); + } + + /// + /// Asserts that the count is greater than or equal to the expected value. + /// Returns a collection-aware assertion that allows further collection chaining. + /// + public CollectionCountEqualsAssertion IsGreaterThanOrEqualTo( + int expected, + [CallerArgumentExpression(nameof(expected))] string? expression = null) + { + _collectionContext.ExpressionBuilder.Append($".IsGreaterThanOrEqualTo({expression})"); + return new CollectionCountEqualsAssertion( + _collectionContext, _assertion, expected, CountComparison.GreaterThanOrEqual); + } + + /// + /// Asserts that the count is less than the expected value. + /// Returns a collection-aware assertion that allows further collection chaining. + /// + public CollectionCountEqualsAssertion IsLessThan( + int expected, + [CallerArgumentExpression(nameof(expected))] string? expression = null) + { + _collectionContext.ExpressionBuilder.Append($".IsLessThan({expression})"); + return new CollectionCountEqualsAssertion( + _collectionContext, _assertion, expected, CountComparison.LessThan); + } + + /// + /// Asserts that the count is less than or equal to the expected value. + /// Returns a collection-aware assertion that allows further collection chaining. + /// + public CollectionCountEqualsAssertion IsLessThanOrEqualTo( + int expected, + [CallerArgumentExpression(nameof(expected))] string? expression = null) + { + _collectionContext.ExpressionBuilder.Append($".IsLessThanOrEqualTo({expression})"); + return new CollectionCountEqualsAssertion( + _collectionContext, _assertion, expected, CountComparison.LessThanOrEqual); + } + + /// + /// Asserts that the count is zero. + /// Returns a collection-aware assertion that allows further collection chaining. + /// + public CollectionCountEqualsAssertion IsZero() + { + _collectionContext.ExpressionBuilder.Append(".IsZero()"); + return new CollectionCountEqualsAssertion( + _collectionContext, _assertion, 0, CountComparison.Equal); + } + + /// + /// Asserts that the count is positive (greater than zero). + /// Returns a collection-aware assertion that allows further collection chaining. + /// + public CollectionCountEqualsAssertion IsPositive() + { + _collectionContext.ExpressionBuilder.Append(".IsPositive()"); + return new CollectionCountEqualsAssertion( + _collectionContext, _assertion, 0, CountComparison.GreaterThan); + } +} + +internal enum CountComparison +{ + Equal, + NotEqual, + GreaterThan, + GreaterThanOrEqual, + LessThan, + LessThanOrEqual +} + +/// +/// Collection-aware count assertion that preserves the collection type for further chaining. +/// Inherits from CollectionAssertionBase to enable .And.Contains(), .And.IsNotEmpty(), etc. +/// +public class CollectionCountEqualsAssertion : CollectionAssertionBase + where TCollection : IEnumerable +{ + private readonly Func, Assertion?>? _itemAssertion; + private readonly int _expected; + private readonly CountComparison _comparison; + private int _actualCount; + + internal CollectionCountEqualsAssertion( + AssertionContext context, + Func, Assertion?>? itemAssertion, + int expected, + CountComparison comparison) + : base(context) + { + _itemAssertion = itemAssertion; + _expected = expected; + _comparison = comparison; + } + + protected override async Task CheckAsync(EvaluationMetadata metadata) + { + var value = metadata.Value; + var exception = metadata.Exception; + + if (exception != null) + { + return AssertionResult.Failed($"threw {exception.GetType().Name}"); + } + + if (value == null) + { + return AssertionResult.Failed("collection was null"); + } + + // Calculate count + if (_itemAssertion == null) + { + // Simple count without filtering + _actualCount = value switch + { + System.Collections.ICollection c => c.Count, + _ => System.Linq.Enumerable.Count(value) + }; + } + else + { + // Count items that satisfy the inner assertion + _actualCount = 0; + int index = 0; + + foreach (var item in value) + { + var itemAssertionSource = new ValueAssertion(item, $"item[{index}]"); + var resultingAssertion = _itemAssertion(itemAssertionSource); + + if (resultingAssertion != null) + { + try + { + await resultingAssertion.AssertAsync(); + _actualCount++; + } + catch + { + // Item did not satisfy the assertion, don't count it + } + } + else + { + // Null assertion means no constraint, count all items + _actualCount++; + } + + index++; + } + } + + // Check the comparison + var passed = _comparison switch + { + CountComparison.Equal => _actualCount == _expected, + CountComparison.NotEqual => _actualCount != _expected, + CountComparison.GreaterThan => _actualCount > _expected, + CountComparison.GreaterThanOrEqual => _actualCount >= _expected, + CountComparison.LessThan => _actualCount < _expected, + CountComparison.LessThanOrEqual => _actualCount <= _expected, + _ => false + }; + + if (passed) + { + return AssertionResult.Passed; + } + + return AssertionResult.Failed($"found {_actualCount}"); + } + + protected override string GetExpectation() + { + var comparisonText = _comparison switch + { + CountComparison.Equal => $"to have count equal to {_expected}", + CountComparison.NotEqual => $"to have count not equal to {_expected}", + CountComparison.GreaterThan => $"to have count greater than {_expected}", + CountComparison.GreaterThanOrEqual => $"to have count greater than or equal to {_expected}", + CountComparison.LessThan => $"to have count less than {_expected}", + CountComparison.LessThanOrEqual => $"to have count less than or equal to {_expected}", + _ => $"to have count {_expected}" + }; + + return comparisonText; + } +} + +/// +/// Collection-aware count assertion that executes an inline count assertion lambda. +/// Preserves the collection type for further chaining. +/// Example: Assert.That(list).Count(c => c.IsEqualTo(5)).And.Contains(1) +/// +public class CollectionCountWithInlineAssertionAssertion : CollectionAssertionBase + where TCollection : IEnumerable +{ + private readonly Func, Assertion?> _countAssertion; + private int _actualCount; + + internal CollectionCountWithInlineAssertionAssertion( + AssertionContext context, + Func, Assertion?> countAssertion) + : base(context) + { + _countAssertion = countAssertion; + } + + protected override async Task CheckAsync(EvaluationMetadata metadata) + { + var value = metadata.Value; + var exception = metadata.Exception; + + if (exception != null) + { + return AssertionResult.Failed($"threw {exception.GetType().Name}"); + } + + if (value == null) + { + return AssertionResult.Failed("collection was null"); + } + + // Calculate count + _actualCount = value switch + { + System.Collections.ICollection c => c.Count, + _ => System.Linq.Enumerable.Count(value) + }; + + // Create an assertion source for the count and run the inline assertion + var countSource = new ValueAssertion(_actualCount, "count"); + var resultingAssertion = _countAssertion(countSource); + + if (resultingAssertion != null) + { + try + { + await resultingAssertion.AssertAsync(); + return AssertionResult.Passed; + } + catch + { + // Count assertion failed + return AssertionResult.Failed($"count was {_actualCount}"); + } + } + + // Null assertion means no constraint, always pass + return AssertionResult.Passed; + } + + protected override string GetExpectation() + { + return "to satisfy count assertion"; + } +} diff --git a/TUnit.Assertions/Conditions/CollectionCountValueAssertion.cs b/TUnit.Assertions/Conditions/CollectionCountValueAssertion.cs deleted file mode 100644 index c47ad6935f..0000000000 --- a/TUnit.Assertions/Conditions/CollectionCountValueAssertion.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System.Collections; -using TUnit.Assertions.Core; -using TUnit.Assertions.Sources; - -namespace TUnit.Assertions.Conditions; - -/// -/// Assertion that evaluates the count of a collection and provides numeric assertions on that count. -/// Implements IAssertionSource<int> to enable all numeric assertion methods. -/// -public class CollectionCountValueAssertion : ValueAssertion - where TCollection : IEnumerable -{ - public CollectionCountValueAssertion( - AssertionContext collectionContext, - Func? predicate) - : base(CreateIntContext(collectionContext, predicate)) - { - } - - private static AssertionContext CreateIntContext( - AssertionContext collectionContext, - Func? predicate) - { - return collectionContext.Map(collection => - { - if (collection == null) - { - return 0; - } - - // Calculate count efficiently - if (predicate == null) - { - return collection switch - { - ICollection c => c.Count, - _ => System.Linq.Enumerable.Count(collection) - }; - } - - return System.Linq.Enumerable.Count(collection, predicate); - }); - } -} - -/// -/// Assertion that evaluates the count of items satisfying an inner assertion and provides numeric assertions on that count. -/// Implements IAssertionSource<int> to enable all numeric assertion methods. -/// This allows using the full assertion builder (e.g., item => item.IsGreaterThan(10)) instead of a simple predicate. -/// -public class CollectionCountWithAssertionValueAssertion : ValueAssertion - where TCollection : IEnumerable -{ - public CollectionCountWithAssertionValueAssertion( - AssertionContext collectionContext, - Func, Assertion?> assertion) - : base(CreateIntContext(collectionContext, assertion)) - { - } - - private static AssertionContext CreateIntContext( - AssertionContext collectionContext, - Func, Assertion?> assertion) - { - return collectionContext.Map(async collection => - { - if (collection == null) - { - return 0; - } - - int count = 0; - int index = 0; - - foreach (var item in collection) - { - var itemAssertion = new ValueAssertion(item, $"item[{index}]"); - var resultingAssertion = assertion(itemAssertion); - - if (resultingAssertion != null) - { - try - { - await resultingAssertion.AssertAsync(); - count++; - } - catch - { - // Item did not satisfy the assertion, don't count it - } - } - else - { - // Null assertion means no constraint, count all items - count++; - } - - index++; - } - - return count; - }); - } -} diff --git a/TUnit.Assertions/Sources/CollectionAssertionBase.cs b/TUnit.Assertions/Sources/CollectionAssertionBase.cs index 96964b17ec..3025455b68 100644 --- a/TUnit.Assertions/Sources/CollectionAssertionBase.cs +++ b/TUnit.Assertions/Sources/CollectionAssertionBase.cs @@ -132,41 +132,43 @@ public CountWrapper HasCount() /// /// Gets the count of items in the collection for further numeric assertions. - /// This enables fluent assertions on the count itself. - /// Example: await Assert.That(list).Count().IsGreaterThan(5); + /// This enables fluent assertions on the count itself while preserving collection type for chaining. + /// Example: await Assert.That(list).Count().IsGreaterThan(5).And.Contains(1); /// - public CollectionCountValueAssertion Count() + public CollectionCountSource Count() { Context.ExpressionBuilder.Append(".Count()"); - return new CollectionCountValueAssertion(Context, null); + return new CollectionCountSource(Context, null); } /// - /// Gets the count of items matching the predicate for further numeric assertions. - /// This enables fluent assertions on filtered counts. - /// Example: await Assert.That(list).Count(x => x > 10).IsEqualTo(3); + /// Asserts on the count of items using an inline assertion lambda. + /// This enables compact count assertions while preserving collection type for chaining. + /// Note: For collections of int, use Count().IsEqualTo(5) instead of Count(c => c.IsEqualTo(5)) + /// to avoid ambiguity with item-filtering. + /// Example: await Assert.That(list).Count(c => c.IsEqualTo(5)).And.Contains(1); /// - [Obsolete("Use Count(item => item.YourAssertion()) instead to leverage the full assertion builder. Example: Count(item => item.IsGreaterThan(10))")] - public CollectionCountValueAssertion Count( - Func predicate, - [CallerArgumentExpression(nameof(predicate))] string? expression = null) + [OverloadResolutionPriority(-1)] + public CollectionCountWithInlineAssertionAssertion Count( + Func, Assertion?> countAssertion, + [CallerArgumentExpression(nameof(countAssertion))] string? expression = null) { Context.ExpressionBuilder.Append($".Count({expression})"); - return new CollectionCountValueAssertion(Context, predicate); + return new CollectionCountWithInlineAssertionAssertion(Context, countAssertion); } /// /// Gets the count of items satisfying the given assertion for further numeric assertions. /// This enables fluent assertions on filtered counts using the full assertion builder. - /// Example: await Assert.That(list).Count(item => item.IsGreaterThan(10)).IsEqualTo(3); + /// The result preserves collection type for further chaining. + /// Example: await Assert.That(list).Count(item => item.IsGreaterThan(10)).IsEqualTo(3).And.Contains(1); /// - [OverloadResolutionPriority(1)] - public CollectionCountWithAssertionValueAssertion Count( - Func, Assertion?> assertion, - [CallerArgumentExpression(nameof(assertion))] string? expression = null) + public CollectionCountSource Count( + Func, Assertion?> itemAssertion, + [CallerArgumentExpression(nameof(itemAssertion))] string? expression = null) { Context.ExpressionBuilder.Append($".Count({expression})"); - return new CollectionCountWithAssertionValueAssertion(Context, assertion); + return new CollectionCountSource(Context, itemAssertion); } /// 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 3fb14f4de1..41743ad69d 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 @@ -421,15 +421,30 @@ namespace .Conditions protected override .<.> CheckAsync(. metadata) { } protected override string GetExpectation() { } } - public class CollectionCountValueAssertion : . + public class CollectionCountEqualsAssertion : . where TCollection : . { - public CollectionCountValueAssertion(. collectionContext, ? predicate) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } } - public class CollectionCountWithAssertionValueAssertion : . + public class CollectionCountSource + where TCollection : . + { + public CollectionCountSource(. collectionContext, <., .?>? assertion) { } + public . IsEqualTo(int expected, [.("expected")] string? expression = null) { } + public . IsGreaterThan(int expected, [.("expected")] string? expression = null) { } + public . IsGreaterThanOrEqualTo(int expected, [.("expected")] string? expression = null) { } + public . IsLessThan(int expected, [.("expected")] string? expression = null) { } + public . IsLessThanOrEqualTo(int expected, [.("expected")] string? expression = null) { } + public . IsNotEqualTo(int expected, [.("expected")] string? expression = null) { } + public . IsPositive() { } + public . IsZero() { } + } + public class CollectionCountWithInlineAssertionAssertion : . where TCollection : . { - public CollectionCountWithAssertionValueAssertion(. collectionContext, <., .?> assertion) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } } [.("DoesNotContain")] public class CollectionDoesNotContainAssertion : . @@ -4371,11 +4386,9 @@ namespace .Sources public . Contains(TItem expected, [.("expected")] string? expression = null) { } public . ContainsOnly( predicate, [.("predicate")] string? expression = null) { } public . Count() { } - [("Use Count(item => ()) instead to leverage the full assertion bu" + - "ilder. Example: Count(item => (10))")] - public . Count( predicate, [.("predicate")] string? expression = null) { } - [.(1)] - public . Count(<., .?> assertion, [.("assertion")] string? expression = null) { } + public . Count(<., .?> itemAssertion, [.("itemAssertion")] string? expression = null) { } + [.(-1)] + public . Count(<., .?> countAssertion, [.("countAssertion")] string? expression = null) { } public . DoesNotContain( predicate, [.("predicate")] string? expression = null) { } public . DoesNotContain(TItem expected, [.("expected")] string? expression = null) { } protected override string GetExpectation() { } 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 af9658c1fa..90595a8661 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 @@ -416,15 +416,30 @@ namespace .Conditions protected override .<.> CheckAsync(. metadata) { } protected override string GetExpectation() { } } - public class CollectionCountValueAssertion : . + public class CollectionCountEqualsAssertion : . where TCollection : . { - public CollectionCountValueAssertion(. collectionContext, ? predicate) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } } - public class CollectionCountWithAssertionValueAssertion : . + public class CollectionCountSource + where TCollection : . + { + public CollectionCountSource(. collectionContext, <., .?>? assertion) { } + public . IsEqualTo(int expected, [.("expected")] string? expression = null) { } + public . IsGreaterThan(int expected, [.("expected")] string? expression = null) { } + public . IsGreaterThanOrEqualTo(int expected, [.("expected")] string? expression = null) { } + public . IsLessThan(int expected, [.("expected")] string? expression = null) { } + public . IsLessThanOrEqualTo(int expected, [.("expected")] string? expression = null) { } + public . IsNotEqualTo(int expected, [.("expected")] string? expression = null) { } + public . IsPositive() { } + public . IsZero() { } + } + public class CollectionCountWithInlineAssertionAssertion : . where TCollection : . { - public CollectionCountWithAssertionValueAssertion(. collectionContext, <., .?> assertion) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } } [.("DoesNotContain")] public class CollectionDoesNotContainAssertion : . @@ -4349,10 +4364,8 @@ namespace .Sources public . Contains(TItem expected, [.("expected")] string? expression = null) { } public . ContainsOnly( predicate, [.("predicate")] string? expression = null) { } public . Count() { } - [("Use Count(item => ()) instead to leverage the full assertion bu" + - "ilder. Example: Count(item => (10))")] - public . Count( predicate, [.("predicate")] string? expression = null) { } - public . Count(<., .?> assertion, [.("assertion")] string? expression = null) { } + public . Count(<., .?> itemAssertion, [.("itemAssertion")] string? expression = null) { } + public . Count(<., .?> countAssertion, [.("countAssertion")] string? expression = null) { } public . DoesNotContain( predicate, [.("predicate")] string? expression = null) { } public . DoesNotContain(TItem expected, [.("expected")] string? expression = null) { } protected override string GetExpectation() { } 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 66d5d3d282..32e1ddc218 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 @@ -421,15 +421,30 @@ namespace .Conditions protected override .<.> CheckAsync(. metadata) { } protected override string GetExpectation() { } } - public class CollectionCountValueAssertion : . + public class CollectionCountEqualsAssertion : . where TCollection : . { - public CollectionCountValueAssertion(. collectionContext, ? predicate) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } } - public class CollectionCountWithAssertionValueAssertion : . + public class CollectionCountSource + where TCollection : . + { + public CollectionCountSource(. collectionContext, <., .?>? assertion) { } + public . IsEqualTo(int expected, [.("expected")] string? expression = null) { } + public . IsGreaterThan(int expected, [.("expected")] string? expression = null) { } + public . IsGreaterThanOrEqualTo(int expected, [.("expected")] string? expression = null) { } + public . IsLessThan(int expected, [.("expected")] string? expression = null) { } + public . IsLessThanOrEqualTo(int expected, [.("expected")] string? expression = null) { } + public . IsNotEqualTo(int expected, [.("expected")] string? expression = null) { } + public . IsPositive() { } + public . IsZero() { } + } + public class CollectionCountWithInlineAssertionAssertion : . where TCollection : . { - public CollectionCountWithAssertionValueAssertion(. collectionContext, <., .?> assertion) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } } [.("DoesNotContain")] public class CollectionDoesNotContainAssertion : . @@ -4371,11 +4386,9 @@ namespace .Sources public . Contains(TItem expected, [.("expected")] string? expression = null) { } public . ContainsOnly( predicate, [.("predicate")] string? expression = null) { } public . Count() { } - [("Use Count(item => ()) instead to leverage the full assertion bu" + - "ilder. Example: Count(item => (10))")] - public . Count( predicate, [.("predicate")] string? expression = null) { } - [.(1)] - public . Count(<., .?> assertion, [.("assertion")] string? expression = null) { } + public . Count(<., .?> itemAssertion, [.("itemAssertion")] string? expression = null) { } + [.(-1)] + public . Count(<., .?> countAssertion, [.("countAssertion")] string? expression = null) { } public . DoesNotContain( predicate, [.("predicate")] string? expression = null) { } public . DoesNotContain(TItem expected, [.("expected")] string? expression = null) { } protected override string GetExpectation() { } 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 0b1c05b81b..0ceb47da88 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 @@ -412,15 +412,30 @@ namespace .Conditions protected override .<.> CheckAsync(. metadata) { } protected override string GetExpectation() { } } - public class CollectionCountValueAssertion : . + public class CollectionCountEqualsAssertion : . where TCollection : . { - public CollectionCountValueAssertion(. collectionContext, ? predicate) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } } - public class CollectionCountWithAssertionValueAssertion : . + public class CollectionCountSource + where TCollection : . + { + public CollectionCountSource(. collectionContext, <., .?>? assertion) { } + public . IsEqualTo(int expected, [.("expected")] string? expression = null) { } + public . IsGreaterThan(int expected, [.("expected")] string? expression = null) { } + public . IsGreaterThanOrEqualTo(int expected, [.("expected")] string? expression = null) { } + public . IsLessThan(int expected, [.("expected")] string? expression = null) { } + public . IsLessThanOrEqualTo(int expected, [.("expected")] string? expression = null) { } + public . IsNotEqualTo(int expected, [.("expected")] string? expression = null) { } + public . IsPositive() { } + public . IsZero() { } + } + public class CollectionCountWithInlineAssertionAssertion : . where TCollection : . { - public CollectionCountWithAssertionValueAssertion(. collectionContext, <., .?> assertion) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } } [.("DoesNotContain")] public class CollectionDoesNotContainAssertion : . @@ -3838,10 +3853,8 @@ namespace .Sources public . Contains(TItem expected, [.("expected")] string? expression = null) { } public . ContainsOnly( predicate, [.("predicate")] string? expression = null) { } public . Count() { } - [("Use Count(item => ()) instead to leverage the full assertion bu" + - "ilder. Example: Count(item => (10))")] - public . Count( predicate, [.("predicate")] string? expression = null) { } - public . Count(<., .?> assertion, [.("assertion")] string? expression = null) { } + public . Count(<., .?> itemAssertion, [.("itemAssertion")] string? expression = null) { } + public . Count(<., .?> countAssertion, [.("countAssertion")] string? expression = null) { } public . DoesNotContain( predicate, [.("predicate")] string? expression = null) { } public . DoesNotContain(TItem expected, [.("expected")] string? expression = null) { } protected override string GetExpectation() { } diff --git a/docs/docs/assertions/collections.md b/docs/docs/assertions/collections.md index f35d0f9a52..fe6e4b6f18 100644 --- a/docs/docs/assertions/collections.md +++ b/docs/docs/assertions/collections.md @@ -164,6 +164,36 @@ public async Task Count_Strings_With_Inner_Assertion() } ``` +### Count with Collection Chaining + +Count assertions preserve the collection type, allowing you to chain additional collection assertions: + +```csharp +[Test] +public async Task Count_With_Chaining() +{ + var numbers = new[] { 1, 2, 3, 4, 5 }; + + // Assert count and then chain with other collection assertions + await Assert.That(numbers) + .Count().IsEqualTo(5) + .And.Contains(3) + .And.IsInOrder(); + + // Count with item filter assertion, then chain + await Assert.That(numbers) + .Count(item => item.IsGreaterThan(2)).IsEqualTo(3) + .And.Contains(5) + .And.All(x => x > 0); + + // For non-int collections, you can also use inline count assertions + var names = new[] { "Alice", "Bob", "Charlie" }; + await Assert.That(names) + .Count(c => c.IsEqualTo(3)) + .And.Contains("Bob"); +} +``` + ### IsEmpty Tests that a collection has no items: