【问题标题】:FluentAssertions 6 ObjectGraph compare Enum to StringFluentAssertions 6 ObjectGraph 将枚举与字符串进行比较
【发布时间】:2022-02-16 06:42:34
【问题描述】:

使用 FluentAssertions 6,您似乎可以在对象图中验证 Enum 是否等效于字符串。来源:https://fluentassertions.com/upgradingtov6

enum MyEnum {
   A,
   B
}

class Source {
   MyEnum Enum { get;set;}
}

class Expectation {
   string Enum { get;set;}
}

var source = new Source() { Enum = MyEnum.A };
var expectation = new Expectation() {Enum = "A"};

//With V6 this assertion will fail but in V5 it will pass
expectation.Should().BeEquivalentTo(source, options => options.ComparingEnumsByName());

如何使用 FluentAssertions 断言上述对象?我想要的行为是对枚举的 ToString 表示进行断言。

正如我旁注的那样,当我将expectationsource 交换时,我会得到不同的行为。它们不应该是等价的吗?

【问题讨论】:

    标签: c# enums fluent-assertions


    【解决方案1】:

    您可以定义一个更宽松的equivalency step 来处理字符串/枚举比较。

    class RelaxedEnumEquivalencyStep : IEquivalencyStep
    {
        public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, IEquivalencyValidator nestedValidator)
        {
            if (comparands.Subject is string subject && comparands.Expectation?.GetType().IsEnum == true)
            {
                AssertionScope.Current
                    .ForCondition(subject == comparands.Expectation.ToString())
                    .FailWith(() =>
                    {
                        decimal? subjectsUnderlyingValue = ExtractDecimal(comparands.Subject);
                        decimal? expectationsUnderlyingValue = ExtractDecimal(comparands.Expectation);
    
                        string subjectsName = GetDisplayNameForEnumComparison(comparands.Subject, subjectsUnderlyingValue);
                        string expectationName = GetDisplayNameForEnumComparison(comparands.Expectation, expectationsUnderlyingValue);
                        return new FailReason(
                                $"Expected {{context:string}} to be equivalent to {expectationName}{{reason}}, but found {subjectsName}.");
                    });
    
                return EquivalencyResult.AssertionCompleted;
            }
    
            if (comparands.Subject?.GetType().IsEnum == true && comparands.Expectation is string expectation)
            {
                AssertionScope.Current
                    .ForCondition(comparands.Subject.ToString() == expectation)
                    .FailWith(() =>
                    {
                        decimal? subjectsUnderlyingValue = ExtractDecimal(comparands.Subject);
                        decimal? expectationsUnderlyingValue = ExtractDecimal(comparands.Expectation);
    
                        string subjectsName = GetDisplayNameForEnumComparison(comparands.Subject, subjectsUnderlyingValue);
                        string expectationName = GetDisplayNameForEnumComparison(comparands.Expectation, expectationsUnderlyingValue);
                        return new FailReason(
                                $"Expected {{context:enum}} to be equivalent to {expectationName}{{reason}}, but found {subjectsName}.");
                    });
    
                return EquivalencyResult.AssertionCompleted;
            }
    
            return EquivalencyResult.ContinueWithNext;
        }
    
        private static string GetDisplayNameForEnumComparison(object o, decimal? v)
        {
            if (o is null)
            {
                return "<null>";
            }
    
            if (v is null)
            {
                return '"' + o.ToString() + '"';
            }
    
            string typePart = o.GetType().Name;
            string namePart = o.ToString().Replace(", ", "|", StringComparison.Ordinal);
            string valuePart = v.Value.ToString(CultureInfo.InvariantCulture);
            return $"{typePart}.{namePart} {{{{value: {valuePart}}}}}";
        }
    
        private static decimal? ExtractDecimal(object o)
        {
            return o?.GetType().IsEnum == true ? Convert.ToDecimal(o, CultureInfo.InvariantCulture) : null;
        }
    }
    

    如果只是一次测试

    var source = new Source() { Enum = MyEnum.A };
    var expectation = new Expectation() { Enum = "A" };
    
    expectation.Should().BeEquivalentTo(source, options => options.Using(new RelaxedEnumEquivalencyStep()));
    
    source.Should().BeEquivalentTo(expectation, options => options.Using(new RelaxedEnumEquivalencyStep()));
    

    或者,如果您想要全局设置,您可以使用AssertionOptions.AssertEquivalencyUsing 进行设置。

    AssertionOptions.AssertEquivalencyUsing(e => e.Using(new RelaxedEnumEquivalencyStep()));
    
    var source = new Source() { Enum = MyEnum.A };
    var expectation = new Expectation() { Enum = "A" };
    
    expectation.Should().BeEquivalentTo(source);
    source.Should().BeEquivalentTo(expectation);
    

    为了完整起见,这里是MyEnumstring 不匹配时的失败消息示例。

    Expected property root.Enum to be equivalent to "B", but found MyEnum.A {value: 0}.
    Expected property source.Enum to be <null>, but found MyEnum.B {value: 1}.
    Expected property root.Enum to be equivalent to MyEnum.A {value: 0}, but found "B".
    Expected property expectation.Enum to be equivalent to MyEnum.B {value: 1}, but found <null>.
    

    【讨论】:

      【解决方案2】:

      您的期望将Enum 属性定义为一个字符串,并且您提供的选项用于指示FA 如何将期望的成员与主题进行比较。所以在这种情况下,ComparingEnumsByName 不做任何事情,因为所涉及的属性是string。您可以做的是使用匿名类型作为期望。

      【讨论】:

      • 感谢回复,但问题是我无法控制来源和预期。
      • 您不能更改单元测试,因此它在您的期望中使用匿名类型?这根本不会影响生产代码。
      • 我使用 AutoFixture 来创建期望,然后将其用于 WireMock 设置。
      • 我现在明白你的意思了 - 是的,我可以在断言之前从我的 AutoFixture 到匿名类型进行额外的转换。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-22
      相关资源
      最近更新 更多