Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restore basic assertions for collections in System.Data #1812

Merged
merged 36 commits into from Apr 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
e5edaec
Updated AssertionExtensions.cs with overloads to pass calls to .Shoul…
logiclrd Feb 16, 2022
96050a7
Added type NonGenericCollectionWrapper.cs in FluentAssertions/Common.…
logiclrd Feb 18, 2022
c2012c6
Corrected a copy/paste error in the XML docs in DataRowCollectionAsse…
logiclrd Feb 18, 2022
19966f6
PR feedback:
logiclrd Feb 20, 2022
9d81c22
Rewrote HaveSameCount and NotHaveSameCount in DataTableCollectionAsse…
logiclrd Feb 20, 2022
20da350
Generalized the implementations of HaveSameCount and NotHaveSameCount…
logiclrd Feb 20, 2022
0617267
Simplified the implementation of HaveSameCount and NotHaveSameCount i…
logiclrd Feb 20, 2022
aac8378
PR feedback related to DataTableCollectionAssertionExtensions.cs, Dat…
logiclrd Feb 20, 2022
baa0c96
Restructured assertions in DataTableCollectionAssertionExtensionsSpec…
logiclrd Feb 20, 2022
a4a7fea
Updated all System.Data collection specs to ensure that the exception…
logiclrd Feb 20, 2022
8eafbce
Eliminated explicit type names from test names in DataTableCollection…
logiclrd Feb 20, 2022
f638849
Reverted partial renaming of "expected" to "expectation".
logiclrd Feb 20, 2022
5ba6b84
Updated BeSameAs assertions in DataTableCollectionAssertionExtensions…
logiclrd Feb 20, 2022
373f5fc
PR feedback:
logiclrd Feb 21, 2022
28cc3cf
Added a null argument guard to the constructor of NonGenericCollectio…
logiclrd Feb 21, 2022
a9a1702
Excised set operations from DataRowCollectionAssertionExtensions.cs a…
logiclrd Feb 21, 2022
7e13fe0
Wrapped lines longer than 130 characters in new System.Data code.
logiclrd Feb 21, 2022
aeb21fb
Removed documentation for set operations from data.md.
logiclrd Feb 21, 2022
63f61e6
Removed ContainEquivalentOf and NotContainEquivalentOf from DataRowCo…
logiclrd Feb 21, 2022
182f514
Corrected assertion failure messages for BeSameAs in DataTableCollect…
logiclrd Feb 21, 2022
1996730
Excised custom Contain and NotContain (by name) from DataTableCollect…
logiclrd Feb 21, 2022
b7f2133
Added tests that cover the situation where the subject is null to Dat…
logiclrd Feb 28, 2022
f496362
Unrolled loops in DataTableCollectionAssertionExtensionsSpecs.cs, Dat…
logiclrd Mar 2, 2022
3e67266
Simplified tests in DataTableCollectionAssertionExtensionsSpecs.cs an…
logiclrd Mar 2, 2022
c3423a6
Removed some additional unnecessary test subject set-up from DataRowC…
logiclrd Mar 2, 2022
316b34a
Eliminated helper functions in the construction of subject and expect…
logiclrd Mar 6, 2022
727bf0e
Added additional tests to DataTableCollectionAssertionExtensionsSpecs…
logiclrd Mar 13, 2022
16e3e0d
Renamed NonGenericCollectionWrapper to ReadOnlyNonGenericCollectionWr…
logiclrd Mar 14, 2022
68ac311
PR review suggestions:
logiclrd Mar 14, 2022
13dfbda
Renamed DataTableCollectionAssertionExtensionsSpecs.cs to DataTableCo…
logiclrd Mar 14, 2022
6ee1de2
Added utility class TypeDescriptionUtility.cs, along with unit tests …
logiclrd Mar 14, 2022
1c3f552
Changed direct InvalidOperationException in BeSameAs and NotBeSameAs …
logiclrd Mar 15, 2022
897f353
Undid the refactoring of FailWith in AssertionScope.cs that produced …
logiclrd Mar 15, 2022
791e8af
Made TypeDescriptionUtility.GetTypeDescription non-discoverable, as n…
logiclrd Mar 16, 2022
02e0679
Moved TypeDescriptionUtility to the Common folder/namespace. Updated …
logiclrd Mar 16, 2022
8c879bf
Baleeted TypeDescriptionUtility, per @dennisdoomen.
logiclrd Mar 17, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
logiclrd marked this conversation as resolved.
Show resolved Hide resolved
.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" />.
logiclrd marked this conversation as resolved.
Show resolved Hide resolved
/// </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))
logiclrd marked this conversation as resolved.
Show resolved Hide resolved
.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);
}
}
}