Skip to content
39 changes: 39 additions & 0 deletions TUnit.Assertions.Should.Tests/CollectionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,45 @@ public async Task Dictionary_ContainKeyWithValue()
await dict.Should().ContainKeyWithValue("one", 1);
}

[Test]
public async Task Dictionary_size_and_count_methods()
{
IReadOnlyDictionary<string, int> dict = new Dictionary<string, int>
{
["one"] = 1,
["two"] = 2,
};

await dict.Should().NotBeEmpty();
await dict.Should().HaveAtLeast(1);
await dict.Should().HaveAtMost(5);
await dict.Should().HaveCountBetween(1, 3);

IReadOnlyDictionary<string, int> empty = new Dictionary<string, int>();
await empty.Should().BeEmpty();

IReadOnlyDictionary<string, int> single = new Dictionary<string, int> { ["only"] = 1 };
await single.Should().HaveSingleItem();
}

[Test]
public async Task MutableDictionary_size_and_count_methods()
{
IDictionary<string, int> dict = new Dictionary<string, int>
{
["one"] = 1,
["two"] = 2,
};

await dict.Should().NotBeEmpty();
await dict.Should().HaveAtLeast(1);
await dict.Should().HaveAtMost(5);
await dict.Should().HaveCountBetween(1, 3);

IDictionary<string, int> single = new Dictionary<string, int> { ["only"] = 1 };
await single.Should().HaveSingleItem();
}

[Test]
public async Task HashSet_BeSupersetOf()
{
Expand Down
108 changes: 108 additions & 0 deletions TUnit.Assertions.Should/Core/ShouldDictionarySource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,61 @@ public ShouldAssertion<IReadOnlyDictionary<TKey, TValue>> AnyValue(
var inner = ApplyBecause(new DictionaryAnyValueAssertion<IReadOnlyDictionary<TKey, TValue>, TKey, TValue>(Context, predicate));
return new ShouldAssertion<IReadOnlyDictionary<TKey, TValue>>(Context, inner);
}

// The count/size methods below are hand-written because the source DictionaryAssertion shadows the
// inherited collection methods (IsEmpty, HasSingleItem, ...) with dictionary-typed `public new`
// overloads whose abstract return type the Should generator can't construct. Without these the
// generated Be/Have counterparts would silently disappear from the dictionary Should surface.

public ShouldAssertion<IReadOnlyDictionary<TKey, TValue>> BeEmpty()
{
Context.ExpressionBuilder.Append(".BeEmpty()");
var inner = ApplyBecause(new CollectionIsEmptyAssertion<IReadOnlyDictionary<TKey, TValue>, KeyValuePair<TKey, TValue>>(Context));
return new ShouldAssertion<IReadOnlyDictionary<TKey, TValue>>(Context, inner);
}

public ShouldAssertion<IReadOnlyDictionary<TKey, TValue>> NotBeEmpty()
{
Context.ExpressionBuilder.Append(".NotBeEmpty()");
var inner = ApplyBecause(new CollectionIsNotEmptyAssertion<IReadOnlyDictionary<TKey, TValue>, KeyValuePair<TKey, TValue>>(Context));
return new ShouldAssertion<IReadOnlyDictionary<TKey, TValue>>(Context, inner);
}

public ShouldAssertion<IReadOnlyDictionary<TKey, TValue>> HaveSingleItem()
{
Context.ExpressionBuilder.Append(".HaveSingleItem()");
var inner = ApplyBecause(new HasSingleItemAssertion<IReadOnlyDictionary<TKey, TValue>, KeyValuePair<TKey, TValue>>(Context));
return new ShouldAssertion<IReadOnlyDictionary<TKey, TValue>>(Context, inner);
}

public ShouldAssertion<IReadOnlyDictionary<TKey, TValue>> HaveAtLeast(
int minCount,
[CallerArgumentExpression(nameof(minCount))] string? expression = null)
{
Context.ExpressionBuilder.Append(".HaveAtLeast(").Append(expression).Append(')');
var inner = ApplyBecause(new CollectionHasAtLeastAssertion<IReadOnlyDictionary<TKey, TValue>, KeyValuePair<TKey, TValue>>(Context, minCount));
return new ShouldAssertion<IReadOnlyDictionary<TKey, TValue>>(Context, inner);
}

public ShouldAssertion<IReadOnlyDictionary<TKey, TValue>> HaveAtMost(
int maxCount,
[CallerArgumentExpression(nameof(maxCount))] string? expression = null)
{
Context.ExpressionBuilder.Append(".HaveAtMost(").Append(expression).Append(')');
var inner = ApplyBecause(new CollectionHasAtMostAssertion<IReadOnlyDictionary<TKey, TValue>, KeyValuePair<TKey, TValue>>(Context, maxCount));
return new ShouldAssertion<IReadOnlyDictionary<TKey, TValue>>(Context, inner);
}

public ShouldAssertion<IReadOnlyDictionary<TKey, TValue>> HaveCountBetween(
int min,
int max,
[CallerArgumentExpression(nameof(min))] string? minExpression = null,
[CallerArgumentExpression(nameof(max))] string? maxExpression = null)
{
Context.ExpressionBuilder.Append($".HaveCountBetween({minExpression}, {maxExpression})");
var inner = ApplyBecause(new CollectionHasCountBetweenAssertion<IReadOnlyDictionary<TKey, TValue>, KeyValuePair<TKey, TValue>>(Context, min, max));
return new ShouldAssertion<IReadOnlyDictionary<TKey, TValue>>(Context, inner);
}
}

[ShouldGeneratePartial(typeof(MutableDictionaryAssertion<,>))]
Expand Down Expand Up @@ -228,4 +283,57 @@ public ShouldAssertion<IDictionary<TKey, TValue>> AnyValue(
var inner = ApplyBecause(new MutableDictionaryAnyValueAssertion<IDictionary<TKey, TValue>, TKey, TValue>(Context, predicate));
return new ShouldAssertion<IDictionary<TKey, TValue>>(Context, inner);
}

// See ShouldDictionarySource: hand-written because MutableDictionaryAssertion shadows the inherited
// collection methods with dictionary-typed `public new` overloads the Should generator can't construct.

public ShouldAssertion<IDictionary<TKey, TValue>> BeEmpty()
{
Context.ExpressionBuilder.Append(".BeEmpty()");
var inner = ApplyBecause(new CollectionIsEmptyAssertion<IDictionary<TKey, TValue>, KeyValuePair<TKey, TValue>>(Context));
return new ShouldAssertion<IDictionary<TKey, TValue>>(Context, inner);
}

public ShouldAssertion<IDictionary<TKey, TValue>> NotBeEmpty()
{
Context.ExpressionBuilder.Append(".NotBeEmpty()");
var inner = ApplyBecause(new CollectionIsNotEmptyAssertion<IDictionary<TKey, TValue>, KeyValuePair<TKey, TValue>>(Context));
return new ShouldAssertion<IDictionary<TKey, TValue>>(Context, inner);
}

public ShouldAssertion<IDictionary<TKey, TValue>> HaveSingleItem()
{
Context.ExpressionBuilder.Append(".HaveSingleItem()");
var inner = ApplyBecause(new HasSingleItemAssertion<IDictionary<TKey, TValue>, KeyValuePair<TKey, TValue>>(Context));
return new ShouldAssertion<IDictionary<TKey, TValue>>(Context, inner);
}

public ShouldAssertion<IDictionary<TKey, TValue>> HaveAtLeast(
int minCount,
[CallerArgumentExpression(nameof(minCount))] string? expression = null)
{
Context.ExpressionBuilder.Append(".HaveAtLeast(").Append(expression).Append(')');
var inner = ApplyBecause(new CollectionHasAtLeastAssertion<IDictionary<TKey, TValue>, KeyValuePair<TKey, TValue>>(Context, minCount));
return new ShouldAssertion<IDictionary<TKey, TValue>>(Context, inner);
}

public ShouldAssertion<IDictionary<TKey, TValue>> HaveAtMost(
int maxCount,
[CallerArgumentExpression(nameof(maxCount))] string? expression = null)
{
Context.ExpressionBuilder.Append(".HaveAtMost(").Append(expression).Append(')');
var inner = ApplyBecause(new CollectionHasAtMostAssertion<IDictionary<TKey, TValue>, KeyValuePair<TKey, TValue>>(Context, maxCount));
return new ShouldAssertion<IDictionary<TKey, TValue>>(Context, inner);
}

public ShouldAssertion<IDictionary<TKey, TValue>> HaveCountBetween(
int min,
int max,
[CallerArgumentExpression(nameof(min))] string? minExpression = null,
[CallerArgumentExpression(nameof(max))] string? maxExpression = null)
{
Context.ExpressionBuilder.Append($".HaveCountBetween({minExpression}, {maxExpression})");
var inner = ApplyBecause(new CollectionHasCountBetweenAssertion<IDictionary<TKey, TValue>, KeyValuePair<TKey, TValue>>(Context, min, max));
return new ShouldAssertion<IDictionary<TKey, TValue>>(Context, inner);
}
}
Loading
Loading