NetFabric.Assertive
This is a assertions library that performs full coverage on any enumerable type and checks edge scenarios that many developers are not aware of.
Syntax
source.Must()
.BeNotNull()
.BeEnumerableOf<int>()
.BeEqualTo(new[] {0, 1, 2, 3, 4});
Enumerables
This framework uses fluent syntax and the combination of the following methods allow the testing of any type of enumerable in a single assertion:
-
BeEnumerableOf<TActualItem>()
- asserts that the typeTActual
, passed in toMust<TActual>()
, is an enumerable that returns a stream of items of typeTActualItem
. -
BeAsyncEnumerableOf<TActualItem>()
- asserts that the typeTActual
, passed in toMust<TActual>()
, is an asynchronous enumerable that returns a stream of items of typeTActualItem
. -
BeObservableOf<TActualItem>()
- asserts that the typeTActual
, passed in toMust<TActual>()
, is an observable that returns a stream of items of typeTActualItem
. -
BeEqualTo<TExpectedItem>(IEnumerable<TExpectedItem> expected)
- asserts that the actual enumerable object contains the same items and in the same order asexpected
. It tests all the enumeration forms implemented by the typeTActual
, passed in toMust<TActual>()
.
Collections can have multiple forms of enumeration. For example; a collection that implements IReadOnlyList<T>
can be enumerated using the indexer, using IEnumerable<T>.GetEnumerator()
, using IEnumerable.GetEnumerator()
and using a public GetEnumerator()
that is not an override of any of these interfaces. There's no guarantee that they all are correctly implemented. The Count
property can also return the wrong value.
NOTE: This project uses NetFabric.CodeAnalysis to handle any kind of enumerable or async enumerable. Check its README for a detailed description.
Here's an example of a collection with multiple possible enumerations and enumerator implementations:
public readonly struct MyRange : IReadOnlyList<int>
{
public MyRange(int count)
{
Count = count;
}
public readonly int Count { get; }
public int this[int index]
{
get
{
if (index < 0 || index >= Count)
ThrowIndexOutOfRangeException();
return index;
static void ThrowIndexOutOfRangeException() => throw new IndexOutOfRangeException();
}
}
public readonly Enumerator GetEnumerator() => new Enumerator(Count);
readonly IEnumerator<int> IEnumerable<int>.GetEnumerator() => new DisposableEnumerator(Count);
readonly IEnumerator IEnumerable.GetEnumerator() => new DisposableEnumerator(Count);
public struct Enumerator
{
readonly int count;
int current;
internal Enumerator(int count)
{
this.count = count;
current = -1;
}
public readonly int Current => current;
public bool MoveNext() => ++current < count;
}
class DisposableEnumerator : IEnumerator<int>
{
readonly int count;
int current;
internal DisposableEnumerator(int count)
{
this.count = count;
current = -1;
}
public int Current => current;
object IEnumerator.Current => current;
public bool MoveNext() => ++current < count;
public void Reset() => current = -1;
public void Dispose() {}
}
}
NOTE: The indexer uses a local function so that the accessor does not throw an exception, making it inlinable. The local function does not add dependencies to the example... ;)
This example has two enumerators to: improve performance, allow casting to an enumerable interface and allow the use of extension methods for collections (like LINQ). It can also be enumerated using the indexer.
The use of BeEnumerableOf<TActualItem>()
assertions validates if all implementations are correct; the multiple GetEnumerator()
methods, the Count
property and the indexer.
By reference return
To make the enumeration independent of any interface, this framework uses reflection to enumerate the items. Invocation of methods that return by reference is only possible in netstandard2.1
so, the validation of this type of enumerable is only possible when running the tests on netcoreapp3.0
.
References
- Enumeration in .NET by Antão Almada
- Performance of value-type vs reference-type enumerators by Antão Almada
Credits
The following open-source projects are used to build and test this project:
License
This project is licensed under the MIT license. See the LICENSE file for more info.