Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions TUnit.Assertions/Collections/CollectionChecks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,48 @@ public static AssertionResult CheckCount<TItem>(ICollectionAdapter<TItem> adapte
return AssertionResult.Failed($"received {actual}");
}

/// <summary>
/// Checks if the collection has at least the specified minimum number of items.
/// </summary>
public static AssertionResult CheckHasAtLeast<TItem>(ICollectionAdapter<TItem> adapter, int minCount)
{
var actual = adapter.Count;
if (actual >= minCount)
{
return AssertionResult.Passed;
}

return AssertionResult.Failed($"found {actual}");
}

/// <summary>
/// Checks if the collection has at most the specified maximum number of items.
/// </summary>
public static AssertionResult CheckHasAtMost<TItem>(ICollectionAdapter<TItem> adapter, int maxCount)
{
var actual = adapter.Count;
if (actual <= maxCount)
{
return AssertionResult.Passed;
}

return AssertionResult.Failed($"found {actual}");
}

/// <summary>
/// Checks if the collection count is between the specified minimum and maximum (inclusive).
/// </summary>
public static AssertionResult CheckHasCountBetween<TItem>(ICollectionAdapter<TItem> adapter, int min, int max)
{
var actual = adapter.Count;
if (actual >= min && actual <= max)
{
return AssertionResult.Passed;
}

return AssertionResult.Failed($"found {actual}");
}

/// <summary>
/// Checks if the collection has exactly one item.
/// </summary>
Expand Down
78 changes: 78 additions & 0 deletions TUnit.Assertions/Conditions/AsyncEnumerableAssertions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,84 @@ protected override AssertionResult CheckMaterialized(List<TItem> items)
protected override string GetExpectation() => $"to have {_expected} items";
}

/// <summary>
/// Asserts that the async enumerable has at least the specified minimum number of items.
/// </summary>
public class AsyncEnumerableHasAtLeastAssertion<TItem> : AsyncEnumerableAssertionConditionBase<TItem>
{
private readonly int _minCount;

public AsyncEnumerableHasAtLeastAssertion(
AssertionContext<IAsyncEnumerable<TItem>> context,
int minCount)
: base(context)
{
_minCount = minCount;
}

protected override AssertionResult CheckMaterialized(List<TItem> items)
{
return items.Count >= _minCount
? AssertionResult.Passed
: AssertionResult.Failed($"found {items.Count} items");
}

protected override string GetExpectation() => $"to have at least {_minCount} item(s)";
}

/// <summary>
/// Asserts that the async enumerable has at most the specified maximum number of items.
/// </summary>
public class AsyncEnumerableHasAtMostAssertion<TItem> : AsyncEnumerableAssertionConditionBase<TItem>
{
private readonly int _maxCount;

public AsyncEnumerableHasAtMostAssertion(
AssertionContext<IAsyncEnumerable<TItem>> context,
int maxCount)
: base(context)
{
_maxCount = maxCount;
}

protected override AssertionResult CheckMaterialized(List<TItem> items)
{
return items.Count <= _maxCount
? AssertionResult.Passed
: AssertionResult.Failed($"found {items.Count} items");
}

protected override string GetExpectation() => $"to have at most {_maxCount} item(s)";
}

/// <summary>
/// Asserts that the async enumerable count is between the specified minimum and maximum (inclusive).
/// </summary>
public class AsyncEnumerableHasCountBetweenAssertion<TItem> : AsyncEnumerableAssertionConditionBase<TItem>
{
private readonly int _min;
private readonly int _max;

public AsyncEnumerableHasCountBetweenAssertion(
AssertionContext<IAsyncEnumerable<TItem>> context,
int min,
int max)
: base(context)
{
_min = min;
_max = max;
}

protected override AssertionResult CheckMaterialized(List<TItem> items)
{
return items.Count >= _min && items.Count <= _max
? AssertionResult.Passed
: AssertionResult.Failed($"found {items.Count} items");
}

protected override string GetExpectation() => $"to have count between {_min} and {_max}";
}

/// <summary>
/// Asserts that the async enumerable contains or does not contain the expected item.
/// </summary>
Expand Down
111 changes: 111 additions & 0 deletions TUnit.Assertions/Conditions/CollectionAssertions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,117 @@ protected override Task<AssertionResult> CheckAsync(EvaluationMetadata<TCollecti
protected override string GetExpectation() => $"to have count {_expectedCount}";
}

/// <summary>
/// Asserts that a collection has at least the specified minimum number of items (count >= minCount).
/// Delegates to CollectionChecks for the actual logic.
/// </summary>
public class CollectionHasAtLeastAssertion<TCollection, TItem> : Sources.CollectionAssertionBase<TCollection, TItem>
where TCollection : IEnumerable<TItem>
{
private readonly int _minCount;

public CollectionHasAtLeastAssertion(
AssertionContext<TCollection> context,
int minCount)
: base(context)
{
_minCount = minCount;
}

protected override Task<AssertionResult> CheckAsync(EvaluationMetadata<TCollection> metadata)
{
if (metadata.Exception != null)
{
return Task.FromResult(AssertionResult.Failed($"threw {metadata.Exception.GetType().Name}"));
}

if (metadata.Value == null)
{
return Task.FromResult(AssertionResult.Failed("collection was null"));
}

var adapter = new EnumerableAdapter<TItem>(metadata.Value);
return Task.FromResult(CollectionChecks.CheckHasAtLeast(adapter, _minCount));
}

protected override string GetExpectation() => $"to have at least {_minCount} item(s)";
}

/// <summary>
/// Asserts that a collection has at most the specified maximum number of items (count <= maxCount).
/// Delegates to CollectionChecks for the actual logic.
/// </summary>
public class CollectionHasAtMostAssertion<TCollection, TItem> : Sources.CollectionAssertionBase<TCollection, TItem>
where TCollection : IEnumerable<TItem>
{
private readonly int _maxCount;

public CollectionHasAtMostAssertion(
AssertionContext<TCollection> context,
int maxCount)
: base(context)
{
_maxCount = maxCount;
}

protected override Task<AssertionResult> CheckAsync(EvaluationMetadata<TCollection> metadata)
{
if (metadata.Exception != null)
{
return Task.FromResult(AssertionResult.Failed($"threw {metadata.Exception.GetType().Name}"));
}

if (metadata.Value == null)
{
return Task.FromResult(AssertionResult.Failed("collection was null"));
}

var adapter = new EnumerableAdapter<TItem>(metadata.Value);
return Task.FromResult(CollectionChecks.CheckHasAtMost(adapter, _maxCount));
}

protected override string GetExpectation() => $"to have at most {_maxCount} item(s)";
}

/// <summary>
/// Asserts that a collection count is between the specified minimum and maximum (inclusive).
/// Delegates to CollectionChecks for the actual logic.
/// </summary>
public class CollectionHasCountBetweenAssertion<TCollection, TItem> : Sources.CollectionAssertionBase<TCollection, TItem>
where TCollection : IEnumerable<TItem>
{
private readonly int _min;
private readonly int _max;

public CollectionHasCountBetweenAssertion(
AssertionContext<TCollection> context,
int min,
int max)
: base(context)
{
_min = min;
_max = max;
}

protected override Task<AssertionResult> CheckAsync(EvaluationMetadata<TCollection> metadata)
{
if (metadata.Exception != null)
{
return Task.FromResult(AssertionResult.Failed($"threw {metadata.Exception.GetType().Name}"));
}

if (metadata.Value == null)
{
return Task.FromResult(AssertionResult.Failed("collection was null"));
}

var adapter = new EnumerableAdapter<TItem>(metadata.Value);
return Task.FromResult(CollectionChecks.CheckHasCountBetween(adapter, _min, _max));
}

protected override string GetExpectation() => $"to have count between {_min} and {_max}";
}

/// <summary>
/// Helper for All().Satisfy() pattern - allows custom assertions on all collection items.
/// </summary>
Expand Down
38 changes: 38 additions & 0 deletions TUnit.Assertions/Sources/AsyncEnumerableAssertionBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,44 @@ public AsyncEnumerableHasCountAssertion<TItem> HasCount(
return new AsyncEnumerableHasCountAssertion<TItem>(Context, expected);
}

/// <summary>
/// Asserts that the async enumerable has at least the specified minimum number of items (count >= minCount).
/// Example: await Assert.That(asyncEnumerable).HasAtLeast(3);
/// </summary>
public AsyncEnumerableHasAtLeastAssertion<TItem> HasAtLeast(
int minCount,
[CallerArgumentExpression(nameof(minCount))] string? expression = null)
{
Context.ExpressionBuilder.Append($".HasAtLeast({expression})");
return new AsyncEnumerableHasAtLeastAssertion<TItem>(Context, minCount);
}

/// <summary>
/// Asserts that the async enumerable has at most the specified maximum number of items (count <= maxCount).
/// Example: await Assert.That(asyncEnumerable).HasAtMost(10);
/// </summary>
public AsyncEnumerableHasAtMostAssertion<TItem> HasAtMost(
int maxCount,
[CallerArgumentExpression(nameof(maxCount))] string? expression = null)
{
Context.ExpressionBuilder.Append($".HasAtMost({expression})");
return new AsyncEnumerableHasAtMostAssertion<TItem>(Context, maxCount);
}

/// <summary>
/// Asserts that the async enumerable count is between the specified minimum and maximum (inclusive).
/// Example: await Assert.That(asyncEnumerable).HasCountBetween(2, 5);
/// </summary>
public AsyncEnumerableHasCountBetweenAssertion<TItem> HasCountBetween(
int min,
int max,
[CallerArgumentExpression(nameof(min))] string? minExpression = null,
[CallerArgumentExpression(nameof(max))] string? maxExpression = null)
{
Context.ExpressionBuilder.Append($".HasCountBetween({minExpression}, {maxExpression})");
return new AsyncEnumerableHasCountBetweenAssertion<TItem>(Context, min, max);
}

/// <summary>
/// Asserts that the async enumerable contains the expected item.
/// Example: await Assert.That(asyncEnumerable).Contains(5);
Expand Down
41 changes: 41 additions & 0 deletions TUnit.Assertions/Sources/CollectionAssertionBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,47 @@ public HasSingleItemPredicateAssertion<TCollection, TItem> HasSingleItem(
return new HasSingleItemPredicateAssertion<TCollection, TItem>(Context, predicate, expression ?? "predicate");
}

/// <summary>
/// Asserts that the collection has at least the specified minimum number of items (count >= minCount).
/// This instance method enables calling HasAtLeast with proper type inference.
/// Example: await Assert.That(list).HasAtLeast(3);
/// </summary>
public CollectionHasAtLeastAssertion<TCollection, TItem> HasAtLeast(
int minCount,
[CallerArgumentExpression(nameof(minCount))] string? expression = null)
{
Context.ExpressionBuilder.Append($".HasAtLeast({expression})");
return new CollectionHasAtLeastAssertion<TCollection, TItem>(Context, minCount);
}

/// <summary>
/// Asserts that the collection has at most the specified maximum number of items (count <= maxCount).
/// This instance method enables calling HasAtMost with proper type inference.
/// Example: await Assert.That(list).HasAtMost(10);
/// </summary>
public CollectionHasAtMostAssertion<TCollection, TItem> HasAtMost(
int maxCount,
[CallerArgumentExpression(nameof(maxCount))] string? expression = null)
{
Context.ExpressionBuilder.Append($".HasAtMost({expression})");
return new CollectionHasAtMostAssertion<TCollection, TItem>(Context, maxCount);
}

/// <summary>
/// Asserts that the collection count is between the specified minimum and maximum (inclusive).
/// This instance method enables calling HasCountBetween with proper type inference.
/// Example: await Assert.That(list).HasCountBetween(2, 5);
/// </summary>
public CollectionHasCountBetweenAssertion<TCollection, TItem> HasCountBetween(
int min,
int max,
[CallerArgumentExpression(nameof(min))] string? minExpression = null,
[CallerArgumentExpression(nameof(max))] string? maxExpression = null)
{
Context.ExpressionBuilder.Append($".HasCountBetween({minExpression}, {maxExpression})");
return new CollectionHasCountBetweenAssertion<TCollection, TItem>(Context, min, max);
}

/// <summary>
/// Asserts that the collection contains only distinct (unique) items.
/// This instance method enables calling HasDistinctItems with proper type inference.
Expand Down
Loading
Loading