Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to combine collection and property assertions using fluent-assertions?

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 SelectListItems (from ASP.NET MVC). I want to assert that actual and expected are "structurally" equal, noting that the reference type SelectListItem does not override Equals 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()
    }
}
like image 591
Stephen Swensen Avatar asked Jan 25 '12 17:01

Stephen Swensen


1 Answers

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.

like image 122
Dennis Doomen Avatar answered Sep 23 '22 18:09

Dennis Doomen