I would like to "combine" Fluent Assertion's collection assertions and property assertions, e.g. assert that two IEnumerable
's are pairwise-equal using property-by-property (possibly "nested") comparison (i.e. structural equality, in functional language parlance).
Concrete example:
var dic = new Dictionary<int, string>() { {1, "hi"}, {2, "bye" } };
var actual = dic.ToSelectListItems(0).OrderBy(si => si.Text);
var expected = new List<SelectListItem>() {
new SelectListItem() {Selected = false, Text="bye", Value="2"},
new SelectListItem() {Selected = false, Text="hi", Value="1"}
};
Here I wrote an extension method ToSelectListItems
that converts a Dictionary
to an IEnumerable
of SelectListItem
s (from ASP.NET MVC). I want to assert that actual
and expected
are "structurally" equal, noting that the reference type SelectListItem
does not override Equal
s and thus uses reference equality by default.
Update
Currently using the following hand-rolled solution, still hoping for something better built into FluentAssertions:
public static void ShouldBeStructurallyEqualTo<T, U>(this IEnumerable<T> actual, IEnumerable<U> expected) {
actual.Should().HaveCount(expected.Count());
actual.Zip(expected).ForEach(pair => pair.Item1.ShouldHave().AllProperties().IncludingNestedObjects().EqualTo(pair.Item2));
}
(note: Zip
here is my own IEnumerable
extention which uses Tuple.Create
as the default projection)
Update 2
Here are two minimal examples:
public class FooBar {
public string Foo { get; set; }
public int Bar { get; set; }
}
public class TestClass {
[Test]
public void MinimalExample() {
List<FooBar> enumerable1 = new List<FooBar>() { new FooBar() { Foo = "x", Bar = 1 }, new FooBar() { Foo = "y", Bar = 2 } };
List<FooBar> enumerable2 = new List<FooBar>() { new FooBar() { Foo = "x", Bar = 1 }, new FooBar() { Foo = "y", Bar = 2 } };
enumerable1.ShouldHave().SharedProperties().IncludingNestedObjects().EqualTo(enumerable2);
//Test 'TestClass.MinimalExample' failed: System.Reflection.TargetParameterCountException : Parameter count mismatch.
// at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks)
// at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
// at System.Reflection.RuntimePropertyInfo.GetValue(Object obj, BindingFlags invokeAttr, Binder binder, Object[] index, CultureInfo culture)
// at System.Reflection.RuntimePropertyInfo.GetValue(Object obj, Object[] index)
// at FluentAssertions.Assertions.PropertyEqualityValidator.AssertSelectedPropertiesAreEqual(Object subject, Object expected)
// at FluentAssertions.Assertions.PropertyEqualityValidator.Validate(UniqueObjectTracker tracker, String parentPropertyName)
// at FluentAssertions.Assertions.PropertyEqualityValidator.Validate()
// at FluentAssertions.Assertions.PropertyAssertions`1.EqualTo(Object otherObject, String reason, Object[] reasonArgs)
// at FluentAssertions.Assertions.PropertyAssertions`1.EqualTo(Object otherObject)
// MiscAssertions.cs(32,0): at TestClass.MinimalExample()
}
[Test]
public void MinimalExample2() {
IEnumerable<FooBar> enumerable1 = (new List<FooBar>() { new FooBar() { Foo = "x", Bar = 1 }, new FooBar() { Foo = "y", Bar = 2 } }).Cast<FooBar>();
FooBar[] enumerable2 = new [] { new FooBar() { Foo = "x", Bar = 1 }, new FooBar() { Foo = "y", Bar = 2 } };
enumerable1.ShouldHave().SharedProperties().IncludingNestedObjects().EqualTo(enumerable2);
//Test 'TestClass.MinimalExample2' failed: System.InvalidOperationException : Please specify some properties to include in the comparison.
// at FluentAssertions.Assertions.PropertyEqualityValidator.Validate(UniqueObjectTracker tracker, String parentPropertyName)
// at FluentAssertions.Assertions.PropertyEqualityValidator.Validate()
// at FluentAssertions.Assertions.PropertyAssertions`1.EqualTo(Object otherObject, String reason, Object[] reasonArgs)
// at FluentAssertions.Assertions.PropertyAssertions`1.EqualTo(Object otherObject)
// MiscAssertions.cs(52,0): at TestClass.MinimalExample2()
}
}
I have added support for your scenario in the main branch of Fluent Assertions. It will be part of the next version, but it might take us a month or two to accumalate enough changes to warrant another release. If you want, you can grab the source build and run the release.bat to build an intermediate version.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With