Skip to content

Comments

feat: add collection count range assertions#4937

Merged
thomhurst merged 4 commits intomainfrom
feat/collection-count-assertions
Feb 19, 2026
Merged

feat: add collection count range assertions#4937
thomhurst merged 4 commits intomainfrom
feat/collection-count-assertions

Conversation

@thomhurst
Copy link
Owner

Summary

  • Adds HasAtLeast(int minCount) assertion: asserts collection has >= minCount items
  • Adds HasAtMost(int maxCount) assertion: asserts collection has <= maxCount items
  • Adds HasCountBetween(int min, int max) assertion: asserts collection count is between min and max (inclusive)
  • Implemented for both IEnumerable<T> and IAsyncEnumerable<T> following existing patterns
  • All assertions support chaining via .And / .Or continuations

Closes #4869

Test plan

  • Verify HasAtLeast passes when collection count >= minCount and fails when count < minCount
  • Verify HasAtMost passes when collection count <= maxCount and fails when count > maxCount
  • Verify HasCountBetween passes when min <= count <= max and fails otherwise
  • Verify chaining works (e.g., .HasAtLeast(2).And.HasAtMost(5))
  • Verify async enumerable variants work correctly
  • Verify null collection handling produces correct failure messages
  • Run public API snapshot tests and update verified files if needed

@claude
Copy link
Contributor

claude bot commented Feb 19, 2026

Code Review

Issue: Missing Public API Snapshot Updates

The PR adds significant new public API surface to TUnit.Assertions but does not include updated .verified.txt snapshot files.

New public types added (6 classes):

  • CollectionHasAtLeastAssertion<TCollection, TItem>
  • CollectionHasAtMostAssertion<TCollection, TItem>
  • CollectionHasCountBetweenAssertion<TCollection, TItem>
  • AsyncEnumerableHasAtLeastAssertion<TItem>
  • AsyncEnumerableHasAtMostAssertion<TItem>
  • AsyncEnumerableHasCountBetweenAssertion<TItem>

New public methods added (9 methods):

  • HasAtLeast, HasAtMost, HasCountBetween on both CollectionAssertionBase and AsyncEnumerableAssertionBase
  • CheckHasAtLeast, CheckHasAtMost, CheckHasCountBetween on CollectionChecks

Per CLAUDE.md:

"Changes to source generator output or public APIs require running snapshot tests. Commit .verified.txt files."

The TUnit.PublicAPI/ directory contains 4 .verified.txt files that track the full public API of TUnit.Assertions (e.g., Tests.Assertions_Library_Has_No_API_Changes.DotNet9_0.verified.txt). None of these were updated in this PR, so the Tests.Assertions_Library_Has_No_API_Changes snapshot tests will fail.

To fix: Run the snapshot tests, then promote the generated .received.txt files to .verified.txt and commit them:

# After running snapshot tests, from the repo root:
for f in TUnit.PublicAPI/*.received.txt; do mv "$f" "${f%.received.txt}.verified.txt"; done
git add TUnit.PublicAPI/*.verified.txt

Overall Assessment

Aside from the missing snapshot updates, the implementation is well-structured and follows the established manual pattern correctly — which is the right approach for collection assertions. The [GenerateAssertion] attribute (preferred for simple scalar assertions) is not applicable here because collection assertions require two generic type parameters (TCollection and TItem), instance methods on CollectionAssertionBase for proper .And/.Or chaining, and internal adapter types (ICollectionAdapter<TItem>, EnumerableAdapter<TItem>) — none of which are supported by the generator. The logic is correct, null/exception handling is consistent with existing assertions, and the CallerArgumentExpression usage is properly wired up.

@thomhurst thomhurst force-pushed the feat/collection-count-assertions branch from 36d06e6 to 1db109f Compare February 19, 2026 08:04
@thomhurst thomhurst force-pushed the feat/collection-count-assertions branch from 1db109f to 42afa53 Compare February 19, 2026 09:38
@thomhurst thomhurst force-pushed the feat/collection-count-assertions branch from 42afa53 to 9d4d050 Compare February 19, 2026 11:46
…asCountBetween)

Add three new collection count assertions for more expressive range checking:
- HasAtLeast(minCount): asserts collection count >= minCount
- HasAtMost(maxCount): asserts collection count <= maxCount
- HasCountBetween(min, max): asserts min <= collection count <= max

Implemented for both IEnumerable<T> (via CollectionAssertionBase) and
IAsyncEnumerable<T> (via AsyncEnumerableAssertionBase), following existing
patterns with CollectionChecks delegation and EnumerableAdapter usage.

Closes #4869
Add verified snapshots for HasAtLeast, HasAtMost, and HasCountBetween
assertion methods added to the collection assertions API.
Add Defaults class, SetRetryBackoff method, BackoffMs/BackoffMultiplier
properties on RetryAttribute, RetryBackoffMs/RetryBackoffMultiplier on
TestDetails and ITestConfiguration - matching the DotNet8_0 snapshot
but without DynamicallyAccessedMembers annotations.
This was referenced Feb 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: add collection count range assertions (HasAtLeast, HasAtMost, HasBetween)

1 participant