Skip to content

Commit

Permalink
Restore basic assertions for collections in System.Data (#1812)
Browse files Browse the repository at this point in the history
  • Loading branch information
logiclrd committed Apr 16, 2022
1 parent 471fb8e commit 6ecd349
Show file tree
Hide file tree
Showing 16 changed files with 2,409 additions and 0 deletions.
31 changes: 31 additions & 0 deletions Src/FluentAssertions/AssertionExtensions.cs
Expand Up @@ -5,6 +5,7 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Net.Http;
using System.Reflection;
Expand Down Expand Up @@ -385,6 +386,36 @@ public static StringCollectionAssertions Should(this IEnumerable<string> @this)
return new GenericDictionaryAssertions<TCollection, TKey, TValue>(actualValue);
}

/// <summary>
/// Returns an assertions object that provides methods for asserting the state of a <see cref="DataTableCollection"/>.
/// </summary>
[Pure]
public static GenericCollectionAssertions<DataTable> Should(this DataTableCollection actualValue)
{
return new GenericCollectionAssertions<DataTable>(
ReadOnlyNonGenericCollectionWrapper.Create(actualValue));
}

/// <summary>
/// Returns an assertions object that provides methods for asserting the state of a <see cref="DataColumnCollection"/>.
/// </summary>
[Pure]
public static GenericCollectionAssertions<DataColumn> Should(this DataColumnCollection actualValue)
{
return new GenericCollectionAssertions<DataColumn>(
ReadOnlyNonGenericCollectionWrapper.Create(actualValue));
}

/// <summary>
/// Returns an assertions object that provides methods for asserting the state of a <see cref="DataRowCollection"/>.
/// </summary>
[Pure]
public static GenericCollectionAssertions<DataRow> Should(this DataRowCollection actualValue)
{
return new GenericCollectionAssertions<DataRow>(
ReadOnlyNonGenericCollectionWrapper.Create(actualValue));
}

/// <summary>
/// Returns a <see cref="DataColumnAssertions"/> object that can be used to assert the
/// current <see cref="DataColumn"/>.
Expand Down
75 changes: 75 additions & 0 deletions Src/FluentAssertions/Common/ReadOnlyNonGenericCollectionWrapper.cs
@@ -0,0 +1,75 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Linq;

namespace FluentAssertions.Common
{
internal static class ReadOnlyNonGenericCollectionWrapper
{
public static ReadOnlyNonGenericCollectionWrapper<DataTableCollection, DataTable> Create(DataTableCollection collection)
{
return
(collection != null)
? new ReadOnlyNonGenericCollectionWrapper<DataTableCollection, DataTable>(collection)
: null;
}

public static ReadOnlyNonGenericCollectionWrapper<DataColumnCollection, DataColumn> Create(DataColumnCollection collection)
{
return
(collection != null)
? new ReadOnlyNonGenericCollectionWrapper<DataColumnCollection, DataColumn>(collection)
: null;
}

public static ReadOnlyNonGenericCollectionWrapper<DataRowCollection, DataRow> Create(DataRowCollection collection)
{
return
(collection != null)
? new ReadOnlyNonGenericCollectionWrapper<DataRowCollection, DataRow>(collection)
: null;
}
}

internal class ReadOnlyNonGenericCollectionWrapper<TCollection, TItem> : ICollection<TItem>
where TCollection : ICollection
{
public TCollection UnderlyingCollection { get; }

public ReadOnlyNonGenericCollectionWrapper(TCollection collection)
{
Guard.ThrowIfArgumentIsNull(collection, nameof(collection));

UnderlyingCollection = collection;
}

public int Count => UnderlyingCollection.Count;

public bool IsReadOnly => true;

public IEnumerator<TItem> GetEnumerator() => UnderlyingCollection.Cast<TItem>().GetEnumerator();

IEnumerator IEnumerable.GetEnumerator() => UnderlyingCollection.GetEnumerator();

public bool Contains(TItem item) => UnderlyingCollection.Cast<TItem>().Contains(item);

public void CopyTo(TItem[] array, int arrayIndex) => UnderlyingCollection.CopyTo(array, arrayIndex);

/*
Mutation is not supported, but these methods must be implemented to satisfy ICollection<T>:
* Add
* Clear
* Remove
*/

public void Add(TItem item) => throw new NotSupportedException();

public void Clear() => throw new NotSupportedException();

public bool Remove(TItem item) => throw new NotSupportedException();
}
}
169 changes: 169 additions & 0 deletions Src/FluentAssertions/DataColumnCollectionAssertionExtensions.cs
@@ -0,0 +1,169 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.Linq;

using FluentAssertions.Collections;
using FluentAssertions.Common;
using FluentAssertions.Execution;

namespace FluentAssertions
{
public static class DataColumnCollectionAssertionExtensions
{
/// <summary>
/// Asserts that an object reference refers to the exact same object as another object reference.
/// </summary>
/// <param name="expected">The expected object</param>
/// <param name="because">
/// A formatted phrase as is supported by <see cref="string.Format(string,object[])" /> explaining why the assertion
/// is needed. If the phrase does not start with the word <i>because</i>, it is prepended automatically.
/// </param>
/// <param name="becauseArgs">
/// Zero or more objects to format using the placeholders in <paramref name="because"/>.
/// </param>
public static AndConstraint<GenericCollectionAssertions<DataColumn>> BeSameAs(
this GenericCollectionAssertions<DataColumn> assertion, DataColumnCollection expected, string because = "",
params object[] becauseArgs)
{
Guard.ThrowIfArgumentIsNull(
expected, nameof(expected), "Cannot verify same reference against a <null> collection (use BeNull instead?).");

if (assertion.Subject is ReadOnlyNonGenericCollectionWrapper<DataColumnCollection, DataColumn> wrapper)
{
var actualSubject = wrapper.UnderlyingCollection;

Execute.Assertion
.UsingLineBreaks
.ForCondition(ReferenceEquals(actualSubject, expected))
.BecauseOf(because, becauseArgs)
.FailWith(
"Expected {context:column collection} to refer to {0}{reason}, but found {1} (different underlying object).",
expected, actualSubject);
}
else
{
Execute.Assertion
.BecauseOf(because, becauseArgs)
.FailWith(
"Invalid expectation: Expected {context:column collection} to refer to an instance of " +
"DataColumnCollection{reason}, but found {0}.",
assertion.Subject);
}

return new AndConstraint<GenericCollectionAssertions<DataColumn>>(assertion);
}

/// <summary>
/// Asserts that an object reference refers to a different object than another object reference refers to.
/// </summary>
/// <param name="unexpected">The unexpected object</param>
/// <param name="because">
/// A formatted phrase as is supported by <see cref="string.Format(string,object[])" /> explaining why the assertion
/// is needed. If the phrase does not start with the word <i>because</i>, it is prepended automatically.
/// </param>
/// <param name="becauseArgs">
/// Zero or more objects to format using the placeholders in <paramref name="because"/>.
/// </param>
public static AndConstraint<GenericCollectionAssertions<DataColumn>> NotBeSameAs(
this GenericCollectionAssertions<DataColumn> assertion, DataColumnCollection unexpected, string because = "",
params object[] becauseArgs)
{
Guard.ThrowIfArgumentIsNull(
unexpected, nameof(unexpected), "Cannot verify same reference against a <null> collection (use NotBeNull instead?).");

if (assertion.Subject is ReadOnlyNonGenericCollectionWrapper<DataColumnCollection, DataColumn> wrapper)
{
var actualSubject = wrapper.UnderlyingCollection;

Execute.Assertion
.UsingLineBreaks
.ForCondition(!ReferenceEquals(actualSubject, unexpected))
.BecauseOf(because, becauseArgs)
.FailWith("Did not expect {context:column collection} to refer to {0}{reason}.", unexpected);
}
else
{
Execute.Assertion
.BecauseOf(because, becauseArgs)
.FailWith(
"Invalid expectation: Expected {context:column collection} to refer to a different instance of " +
"DataColumnCollection{reason}, but found {0}.",
assertion.Subject);
}

return new AndConstraint<GenericCollectionAssertions<DataColumn>>(assertion);
}

/// <summary>
/// Assert that the current collection has the same number of elements as <paramref name="otherCollection" />.
/// </summary>
/// <param name="otherCollection">The other collection with the same expected number of elements</param>
/// <param name="because">
/// A formatted phrase as is supported by <see cref="string.Format(string,object[])" /> explaining why the assertion
/// is needed. If the phrase does not start with the word <i>because</i>, it is prepended automatically.
/// </param>
/// <param name="becauseArgs">
/// Zero or more objects to format using the placeholders in <paramref name="because" />.
/// </param>
public static AndConstraint<GenericCollectionAssertions<DataColumn>> HaveSameCount(
this GenericCollectionAssertions<DataColumn> assertion, DataColumnCollection otherCollection, string because = "",
params object[] becauseArgs)
{
Guard.ThrowIfArgumentIsNull(
otherCollection, nameof(otherCollection), "Cannot verify count against a <null> collection.");

Execute.Assertion
.BecauseOf(because, becauseArgs)
.WithExpectation("Expected {context:collection} to have ")
.Given(() => assertion.Subject)
.ForCondition(subject => subject is not null)
.FailWith("the same count as {0}{reason}, but found <null>.", otherCollection)
.Then
.Given((subject) => (actual: subject.Count(), expected: otherCollection.Count))
.ForCondition(count => count.actual == count.expected)
.FailWith("{0} column(s){reason}, but found {1}.", count => count.expected, count => count.actual)
.Then
.ClearExpectation();

return new AndConstraint<GenericCollectionAssertions<DataColumn>>(assertion);
}

/// <summary>
/// Assert that the current collection of <see cref="DataColumn"/>s does not have the same number of columns as
/// <paramref name="otherCollection" />.
/// </summary>
/// <param name="otherCollection">The other <see cref="DataColumnCollection"/> with the unexpected number of
/// elements</param>
/// <param name="because">
/// A formatted phrase as is supported by <see cref="string.Format(string,object[])" /> explaining why the assertion
/// is needed. If the phrase does not start with the word <i>because</i>, it is prepended automatically.
/// </param>
/// <param name="becauseArgs">
/// Zero or more objects to format using the placeholders in <paramref name="because" />.
/// </param>
public static AndConstraint<GenericCollectionAssertions<DataColumn>> NotHaveSameCount(
this GenericCollectionAssertions<DataColumn> assertion, DataColumnCollection otherCollection, string because = "",
params object[] becauseArgs)
{
Guard.ThrowIfArgumentIsNull(
otherCollection, nameof(otherCollection), "Cannot verify count against a <null> collection.");

Execute.Assertion
.BecauseOf(because, becauseArgs)
.WithExpectation("Expected {context:collection} to not have ")
.Given(() => assertion.Subject)
.ForCondition(subject => subject is not null)
.FailWith("the same count as {0}{reason}, but found <null>.", otherCollection)
.Then
.Given((subject) => (actual: subject.Count(), expected: otherCollection.Count))
.ForCondition(count => count.actual != count.expected)
.FailWith("{0} column(s){reason}, but found {1}.", count => count.expected, count => count.actual)
.Then
.ClearExpectation();

return new AndConstraint<GenericCollectionAssertions<DataColumn>>(assertion);
}
}
}

0 comments on commit 6ecd349

Please sign in to comment.