如何使用流利的断言结合集合和属性断言?

Posted

技术标签:

【中文标题】如何使用流利的断言结合集合和属性断言?【英文标题】:How to combine collection and property assertions using fluent-assertions? 【发布时间】:2012-02-18 21:06:30 【问题描述】:

我想“组合”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 转换为SelectListItems 的IEnumerable(来自ASP.NET MVC)。我想断言actualexpected 在“结构上”是相等的,注意引用类型SelectListItem 不会覆盖Equals,因此默认使用引用相等。

更新

目前使用以下手动解决方案,仍然希望 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()
    

【问题讨论】:

【参考方案1】:

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

【讨论】:

我修复了 Stephens 的变通方法以同时使用值类型,如果 T 是值类型,则如果没有此修复,它将无法工作:gist.github.com/1877672 2.0 公测版fluentassertions.codeplex.com/releases/view/82423 它是如何支持的?我找不到文档示例。【参考方案2】:

如果我正确解释了您的问题,我认为您应该尝试使用 Fluent Assertions 的 1.7.0 版本。在那个版本中,我们更改了使用 IncludeNestedObjects 时的行为,它也会对对象集合执行此操作。文档摘录。

“此外,您可以通过包含 IncludeNestedObjects 属性将结构比较进一步提升。这将指示比较比较主题(在此示例中)的属性引用的所有(集合)复杂类型。默认情况下,它将断言主体的嵌套属性与预期对象的嵌套属性匹配。但是,如果您指定 SharedProperties,那么它只会比较嵌套对象之间的同名属性。例如:

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

【讨论】:

嗨,丹尼斯,感谢您的关注。我使用的是 1.7.0,但问题是(使用您的示例)dtocustomer 都是 IEnumerable&lt;'T&gt; 的子类,因此没有任何共享属性(它们包含的元素有,但不是IEnumerable 本身)。因此我得到System.InvalidOperationException : Please specify some properties to include in the comparison. at FluentAssertions.Assertions.PropertyEqualityValidator.Validate(UniqueObjectTracker tracker, String parentPropertyName) 那么您的意思是actualexpected 都表示具有不完全相同但具有相同属性的对象的可枚举集合? 那么 IncludeNestedObjects() 应该可以工作。你能分享你的 Zip() 和 ToSelectListItems() 方法的代码吗?我想重现您的确切情况,看看为什么 FA 不能正确支持这种情况。 对不起,我的回复迟了,过去几周一直很忙!我在原始问题(更新 2)中添加了两个最小失败的示例。谢谢。 现在我终于明白你的问题了。您正在比较两个集合,而不是包含一个集合的两个对象。我认为支持这一点可能很有用,因此我添加了相应的 CodePlex 问题:fluentassertions.codeplex.com/workitem/11743

以上是关于如何使用流利的断言结合集合和属性断言?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用流利断言断言集合中的所有项目?

如何使用流利的断言比较列表?

流利的断言 - 如何在类型检查后正确链接

如何使用流利的断言报告对象的名称

流利的断言:大约比较两个数字集合

如何使用流利的断言比较对象图中的嵌套列表