How to combine collection and property assertions using fluent-assertions?
Asked Answered
S

2

10

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()
    }
}
Scoter answered 25/1, 2012 at 17:5 Comment(0)
J
7

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.

Johnsiejohnson answered 16/2, 2012 at 19:47 Comment(3)
I fixed Stephens workaround to work with value-types aswell, if T was a value-type it didn't work without this fix: gist.github.com/1877672Channing
It's in the public beta of version 2.0 fluentassertions.codeplex.com/releases/view/82423Johnsiejohnson
And how is it supported ? I cannot find a documentation example.Loyola
J
7

If I'm interpreting your question correctly, I think you should try version 1.7.0 of Fluent Assertions. In that version we changed the behavior that when IncludingNestedObjects is used, it will also do that on collections of objects. An excerpt of the documentation.

"Additionally, you can take structural comparison a level further by including the IncludingNestedObjects property. This will instruct the comparison to compare all (collections of) complex types that the properties of the subject (in this example) refer to. By default, it will assert that the nested properties of the subject match the nested properties of the expected object. However, if you do specify SharedProperties, then it will only compare the equally named properties between the nested objects. For instance:

dto.ShouldHave().SharedProperties().IncludingNestedObjects.EqualTo(customer);"

Johnsiejohnson answered 26/1, 2012 at 7:36 Comment(6)
Hi Dennis, thanks for your attention. I am using 1.7.0, but the problem is that (using your example) dto and customer are both subclasses of IEnumerable<'T> and therefore don't have any shared properties (the elements they contain do, but not the IEnumerable's themselves). Thus I am getting System.InvalidOperationException : Please specify some properties to include in the comparison. at FluentAssertions.Assertions.PropertyEqualityValidator.Validate(UniqueObjectTracker tracker, String parentPropertyName)Scoter
So do you mean that both actual and expected represent enumerable collections with objects that are not exactly the same, but have the same properties?Johnsiejohnson
Then IncludingNestedObjects() should work. Can you share the code of your Zip() and ToSelectListItems() methods? I'd like to reproduce your exact scenario and see why FA is not supporting this scenario properly.Johnsiejohnson
sorry for my belated response, it's been pretty busy the past few weeks! I've added two minimally failing examples to my original question (Update 2). Thanks.Scoter
Now I finally get your problem. You were comparing two collections instead of two objects that contained a collection. I think it could be useful to support this, so I've added a corresponding CodePlex issue:fluentassertions.codeplex.com/workitem/11743Johnsiejohnson
great! sorry I wasn't more clear from the start (using custom methods and var, etc.)!Scoter
J
7

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.

Johnsiejohnson answered 16/2, 2012 at 19:47 Comment(3)
I fixed Stephens workaround to work with value-types aswell, if T was a value-type it didn't work without this fix: gist.github.com/1877672Channing
It's in the public beta of version 2.0 fluentassertions.codeplex.com/releases/view/82423Johnsiejohnson
And how is it supported ? I cannot find a documentation example.Loyola

© 2022 - 2024 — McMap. All rights reserved.