使用FluentAssertions测试可选的等效项 更新

问题描述

我正在使用名为Optionalhttps://github.com/nlkl/Optional)的库,该库允许功能语言通用的“也许”抽象。

该库很棒,但是我面临有关测试的问题:我无法正确测试两个可选实例是否等效。

为了测试是否等效,我使用了Fluent Assertions。但是,我没有得到想要的结果。

我将用代码说明问题:

#load "xunit"

[Fact]
void TestOptional()
{
    var a = new[] { 1,2,3 }.some();
    var b = new[] { 1,3 }.some();
    
    a.Should().BeEquivalentTo(b);
}

如屏幕截图所示,该测试失败(为方便起见,我使用LINQPad)

enter image description here

如您所见,这不是人们期望的。

如何告诉Fluent断言使用Option类型正确检查等效性?

解决方法

更新

I opened an issue on Github关于您的问题,昨天是pull request was merged,因此下一(预)发行版应使您能够优雅地解决问题:

新的重载使您可以使用开放的泛型类型。 如果同时指定了开放式和封闭式,则封闭式优先。

SelfReferenceEquivalencyAssertionOptions添加了以下方法:

  • public TSelf ComparingByMembers(System.Type type) { }
  • public TSelf ComparingByValue(System.Type type) { }

Here's the unit test已添加到Fluent断言中,以显示其工作原理:

[Fact]
public void When_comparing_an_open_type_by_members_it_should_succeed()
{
    // Arrange
    var subject = new Option<int[]>(new[] { 1,3,2 });
    var expected = new Option<int[]>(new[] { 1,2,3 });

    // Act
    Action act = () => subject.Should().BeEquivalentTo(expected,opt => opt
        .ComparingByMembers(typeof(Option<>)));

    // Assert
    act.Should().NotThrow();
}

Fluent Assertions - Object Graph Comparison说:

值类型

确定是否应将Fluent断言重复出现在对象的 属性或字段,它需要了解哪些类型具有价值 语义以及应将哪些类型视为引用类型。的 默认行为是对待覆盖Object.Equals的每种类型 作为旨在具有值语义的对象。不幸, 匿名类型和元组也重写此方法,但是因为我们 倾向于在等效性比较中经常使用它们,我们总是 比较它们的属性。

您可以使用ComparingByValue<T>或 各个断言的ComparingByMembers<T>选项

Option<T>struct,并且覆盖Equals,因此Fluent断言将ab与值语义进行比较。

Option<T>这样实现Equals

public bool Equals(Option<T> other)
{
  if (!this.hasValue && !other.hasValue)
    return true;
  return this.hasValue
    && other.hasValue 
    && EqualityComparer<T>.Default.Equals(this.value,other.value);
}

因此,int[]通过引用进行比较,您的测试失败。

您可以分别为每个测试覆盖此行为,例如Guro Stron说:

a.Should().BeEquivalentTo(b,opt => opt.ComparingByMembers<Option<int[]>>());

或通过静态AssertionOptions类在全局范围内:

AssertionOptions.AssertEquivalencyUsing(options => 
    options.ComparingByMembers<Option<int[]>>());

编辑:

对于您的情况,Fluent断言将需要一个AssertEquivalencyUsing覆盖来支持未绑定的泛型类型:

AssertionOptions.AssertEquivalencyUsing(options => 
    options.ComparingByMembers(typeof(Option<>)));

不幸的是,不存在这样的替代。

提出的另一种解决方案是扩展方法。这里是一个非常简单的实现:

public static class FluentAssertionsExtensions
{
    public static void BeEquivalentByMembers<TExpectation>(
        this ComparableTypeAssertions<TExpectation> actual,TExpectation expectation)
    {
        actual.BeEquivalentTo(
            expectation,options => options.ComparingByMembers<TExpectation>());
    }
}