如何使用流畅断言(fluent-assertions)结合集合和属性断言?

10
我希望将Fluent Assertion的集合断言和属性断言“结合”起来,例如使用按属性(可能是“嵌套”)比较(即功能语言术语中的结构相等)来断言两个IEnumerable是否成对相等。
具体示例:
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"}
};

在这里,我编写了一个扩展方法ToSelectListItems,将Dictionary转换为IEnumerable中的SelectListItem(来自ASP.NET MVC)。 我想断言actualexpected在结构上相等,注意引用类型SelectListItem没有覆盖Equal,因此默认情况下使用引用相等性。

更新

目前使用以下手动解决方案,仍希望有更好的内置于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));
}

(注:这里的 Zip 是我自己编写的IEnumerable扩展,它使用Tuple.Create作为默认投影)

更新2

这里有两个最小的示例:

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()
    }
}
2个回答

7
如果我正确理解了您的问题,我认为您应该尝试使用Fluent Assertions的1.7.0版本。在该版本中,我们更改了当使用IncludingNestedObjects时的行为,它还会对对象集合执行此操作。以下是文档的摘录。
"此外,您可以通过包括IncludingNestedObjects属性将结构比较提升到更高级别。这将指示比较器比较主题(在此示例中)属性引用的所有(集合)复杂类型。默认情况下,它将断言主题的嵌套属性与期望对象的嵌套属性匹配。但是,如果您指定SharedProperties,则仅比较嵌套对象之间同名的属性。例如:

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


嗨Dennis,感谢您的关注。我正在使用1.7.0版本,但问题在于(使用您的示例)dtocustomer都是IEnumerable<'T>的子类,因此它们没有任何共享属性(它们包含的元素有,但不是IEnumerable本身)。因此,我得到了System.InvalidOperationException:请指定要包括在比较中的一些属性。 at FluentAssertions.Assertions.PropertyEqualityValidator.Validate(UniqueObjectTracker tracker, String parentPropertyName) - Stephen Swensen
那么您的意思是actualexpected都表示可枚举的集合,其中的对象并不完全相同,但具有相同的属性吗? - Dennis Doomen
那么包含嵌套对象的IncludingNestedObjects()方法应该可以工作。你能分享一下你Zip()和ToSelectListItems()方法的代码吗?我想复现你的具体情况并看看为什么FA没有正确支持这种情况。 - Dennis Doomen
抱歉回复晚了,最近几周非常忙!我已经在我的原始问题中添加了两个最小化失败的示例(更新2)。谢谢。 - Stephen Swensen
现在我终于明白你的问题了。你正在比较两个包含集合的对象,而不是两个集合本身。我认为支持这种情况可能会很有用,因此我已经添加了一个相应的 CodePlex 问题:http://fluentassertions.codeplex.com/workitem/11743 - Dennis Doomen
太好了!很抱歉我一开始没有表达清楚(使用自定义方法和var等)! - Stephen Swensen

7

我已经在Fluent Assertions的主分支中添加了对你的情境的支持。它将成为下一个版本的一部分,但可能需要我们一个月或两个月才能积累足够的变化来保证另一个发布。如果您愿意,您可以获取源构建并运行release.bat以构建中间版本。


1
我修复了Stephen的解决方法,使其也适用于值类型。如果T是值类型,则需要此修复才能正常工作:https://gist.github.com/1877672 - Simon Stender Boisen
它在2.0版本的公共测试版中。http://fluentassertions.codeplex.com/releases/view/82423 - Dennis Doomen
3
它是如何支持的?我找不到相关文档示例。 - ferpega

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接