From bbf703e29624b2179310ca4d7d0aa101839ed849 Mon Sep 17 00:00:00 2001 From: Artur Krajewski Date: Thu, 27 Sep 2018 07:00:25 +0200 Subject: [PATCH] Improve enum comparison assert messages (#923) --- .../Equivalency/EnumEqualityStep.cs | 70 +++++++++++++++-- Tests/Shared.Specs/BasicEquivalencySpecs.cs | 44 ++++++++--- Tests/Shared.Specs/EnumAssertionSpecs.cs | 75 ++++++++++++++++++- 3 files changed, 168 insertions(+), 21 deletions(-) diff --git a/Src/FluentAssertions/Equivalency/EnumEqualityStep.cs b/Src/FluentAssertions/Equivalency/EnumEqualityStep.cs index fa383a707f..a012191e78 100644 --- a/Src/FluentAssertions/Equivalency/EnumEqualityStep.cs +++ b/Src/FluentAssertions/Equivalency/EnumEqualityStep.cs @@ -1,7 +1,9 @@ #region using System; +using System.Globalization; using System.Reflection; +using FluentAssertions.Execution; #endregion @@ -36,17 +38,11 @@ public bool CanHandle(IEquivalencyValidationContext context, IEquivalencyAsserti switch (config.EnumEquivalencyHandling) { case EnumEquivalencyHandling.ByValue: - decimal? subjectsUnderlyingValue = (context.Subject != null) ? Convert.ToDecimal(context.Subject) : (decimal?)null; - decimal? expectationsUnderlyingValue = (context.Expectation != null) ? Convert.ToDecimal(context.Expectation) : (decimal?)null; - - subjectsUnderlyingValue.Should().Be(expectationsUnderlyingValue, context.Because, context.BecauseArgs); + HandleByValue(context); break; case EnumEquivalencyHandling.ByName: - string subject = context.Subject.ToString(); - string expected = context.Expectation.ToString(); - - subject.Should().Be(expected, context.Because, context.BecauseArgs); + HandleByName(context); break; default: @@ -56,5 +52,63 @@ public bool CanHandle(IEquivalencyValidationContext context, IEquivalencyAsserti return true; } + + private static void HandleByValue(IEquivalencyValidationContext context) + { + decimal? subjectsUnderlyingValue = ExtractDecimal(context.Subject); + decimal? expectationsUnderlyingValue = ExtractDecimal(context.Expectation); + + Execute.Assertion + .ForCondition(subjectsUnderlyingValue == expectationsUnderlyingValue) + .FailWith(() => + { + string subjectsName = GetDisplayNameForEnumComparison(context.Subject, subjectsUnderlyingValue); + string expectationName = GetDisplayNameForEnumComparison(context.Expectation, expectationsUnderlyingValue); + + return new FailReason($"Expected {{context:enum}} to equal {expectationName} by value{{reason}}, but found {subjectsName}."); + }); + } + + private static void HandleByName(IEquivalencyValidationContext context) + { + string subject = context.Subject?.ToString(); + string expected = context.Expectation.ToString(); + + Execute.Assertion + .ForCondition(subject == expected) + .FailWith(() => + { + decimal? subjectsUnderlyingValue = ExtractDecimal(context.Subject); + decimal? expectationsUnderlyingValue = ExtractDecimal(context.Expectation); + + string subjectsName = GetDisplayNameForEnumComparison(context.Subject, subjectsUnderlyingValue); + string expectationName = GetDisplayNameForEnumComparison(context.Expectation, expectationsUnderlyingValue); + return new FailReason( + $"Expected {{context:enum}} to equal {expectationName} by name{{reason}}, but found {subjectsName}."); + }); + } + + private static string GetDisplayNameForEnumComparison(object o, decimal? v) + { + if (o == null || v == null) + { + return "null"; + } + + if (o.GetType().GetTypeInfo().IsEnum) + { + string typePart = o.GetType().Name; + string namePart = Enum.GetName(o.GetType(), o); + string valuePart = v.Value.ToString(CultureInfo.InvariantCulture); + return $"{typePart}.{namePart}({valuePart})"; + } + + return v.Value.ToString(CultureInfo.InvariantCulture); + } + + private static decimal? ExtractDecimal(object o) + { + return o != null ? Convert.ToDecimal(o) : (decimal?)null; + } } } diff --git a/Tests/Shared.Specs/BasicEquivalencySpecs.cs b/Tests/Shared.Specs/BasicEquivalencySpecs.cs index 02545a3f27..808133b8bf 100644 --- a/Tests/Shared.Specs/BasicEquivalencySpecs.cs +++ b/Tests/Shared.Specs/BasicEquivalencySpecs.cs @@ -3532,11 +3532,7 @@ public void When_asserting_different_enum_members_are_equivalent_it_should_fail( // Assert //----------------------------------------------------------------------------------------------------------- act.Should().Throw() -#if NETCOREAPP1_1 - .WithMessage("Expected value to be*3*, but found*0*"); -#else - .WithMessage("Expected EnumOne.One to be*3*, but found*0*"); -#endif + .WithMessage("Expected *EnumOne.Two(3)*but*EnumOne.One(0)*"); } [Fact] @@ -3566,7 +3562,7 @@ public void When_asserting_members_from_different_enum_types_are_equivalent_by_v // Arrange //----------------------------------------------------------------------------------------------------------- var subject = new ClassWithEnumOne { Enum = EnumOne.One }; - var expectation = new ClassWithEnumThree { Enum = EnumeThree.ValueZero }; + var expectation = new ClassWithEnumThree { Enum = EnumThree.ValueZero }; //----------------------------------------------------------------------------------------------------------- // Act @@ -3586,7 +3582,7 @@ public void When_asserting_members_from_different_enum_types_are_equivalent_by_s // Arrange //----------------------------------------------------------------------------------------------------------- var subject = new ClassWithEnumOne { Enum = EnumOne.Two }; - var expectation = new ClassWithEnumThree { Enum = EnumeThree.Two }; + var expectation = new ClassWithEnumThree() { Enum = EnumThree.Two}; //----------------------------------------------------------------------------------------------------------- // Act @@ -3599,6 +3595,26 @@ public void When_asserting_members_from_different_enum_types_are_equivalent_by_s act.Should().NotThrow(); } + [Fact] + public void When_asserting_members_from_different_enum_types_are_equivalent_by_value_but_comparing_by_name_it_should_throw() + { + //----------------------------------------------------------------------------------------------------------- + // Arrange + //----------------------------------------------------------------------------------------------------------- + var subject = new ClassWithEnumOne { Enum = EnumOne.Two }; + var expectation = new ClassWithEnumFour { Enum = EnumFour.Three }; + + //----------------------------------------------------------------------------------------------------------- + // Act + //----------------------------------------------------------------------------------------------------------- + Action act = () => subject.Should().BeEquivalentTo(expectation, config => config.ComparingEnumsByName()); + + //----------------------------------------------------------------------------------------------------------- + // Assert + //----------------------------------------------------------------------------------------------------------- + act.Should().Throw().WithMessage("Expected*to equal EnumFour.Three(3) by name, but found EnumOne.Two(3)*"); + } + [Fact] public void When_asserting_members_from_different_char_enum_types_are_equivalent_by_value_it_should_succeed() { @@ -3830,12 +3846,17 @@ internal enum EnumTwo Two = 3 } - internal enum EnumeThree + internal enum EnumThree { ValueZero = 0, Two = 3 } + internal enum EnumFour + { + Three = 3 + } + internal class ClassWithEnumCharOne { public EnumCharOne Enum { get; set; } @@ -3858,7 +3879,12 @@ internal class ClassWithEnumTwo internal class ClassWithEnumThree { - public EnumeThree Enum { get; set; } + public EnumThree Enum { get; set; } + } + + internal class ClassWithEnumFour + { + public EnumFour Enum { get; set; } } internal class ClassWithNoMembers diff --git a/Tests/Shared.Specs/EnumAssertionSpecs.cs b/Tests/Shared.Specs/EnumAssertionSpecs.cs index c8048234ec..7f1d2f57a9 100644 --- a/Tests/Shared.Specs/EnumAssertionSpecs.cs +++ b/Tests/Shared.Specs/EnumAssertionSpecs.cs @@ -22,43 +22,110 @@ public class EnumAssertionSpecs [Fact] public void When_both_enums_are_equal_and_greater_than_max_long_it_should_not_throw() { + //----------------------------------------------------------------------------------------------------------- // Arrange + //----------------------------------------------------------------------------------------------------------- var enumOne = EnumULong.UInt64Max; var enumTwo = EnumULong.UInt64Max; + //----------------------------------------------------------------------------------------------------------- // Act + //----------------------------------------------------------------------------------------------------------- Action act = () => enumOne.Should().BeEquivalentTo(enumTwo); + //----------------------------------------------------------------------------------------------------------- // Assert + //----------------------------------------------------------------------------------------------------------- act.Should().NotThrow(); } [Fact] public void When_both_enums_are_equal_and_of_different_underlying_types_it_should_not_throw() { + //----------------------------------------------------------------------------------------------------------- // Arrange + //----------------------------------------------------------------------------------------------------------- var enumOne = EnumLong.Int64Max; var enumTwo = EnumULong.Int64Max; + //----------------------------------------------------------------------------------------------------------- // Act + //----------------------------------------------------------------------------------------------------------- Action act = () => enumOne.Should().BeEquivalentTo(enumTwo); + //----------------------------------------------------------------------------------------------------------- // Assert + //----------------------------------------------------------------------------------------------------------- act.Should().NotThrow(); } [Fact] public void When_both_enums_are_large_and_not_equal_it_should_throw() { + //----------------------------------------------------------------------------------------------------------- // Arrange - var enumOne = EnumLong.Int64LessOne; - var enumTwo = EnumULong.UInt64Max; + //----------------------------------------------------------------------------------------------------------- + var subjectEnum = EnumLong.Int64LessOne; + var expectedEnum = EnumULong.UInt64Max; + //----------------------------------------------------------------------------------------------------------- // Act - Action act = () => enumOne.Should().BeEquivalentTo(enumTwo); + //----------------------------------------------------------------------------------------------------------- + Action act = () => subjectEnum.Should().BeEquivalentTo(expectedEnum, "comparing enums should throw"); + + //----------------------------------------------------------------------------------------------------------- + // Assert + //----------------------------------------------------------------------------------------------------------- + act.Should().Throw() +#if NETCOREAPP1_1 + .WithMessage($"Expected enum to equal EnumULong.UInt64Max({(UInt64)EnumULong.UInt64Max}) by value because comparing enums should throw, but found EnumLong.Int64LessOne({(Int64)EnumLong.Int64LessOne})*"); +#else + .WithMessage($"Expected subjectEnum to equal EnumULong.UInt64Max({(UInt64)EnumULong.UInt64Max}) by value because comparing enums should throw, but found EnumLong.Int64LessOne({(Int64)EnumLong.Int64LessOne})*"); +#endif + } + + [Fact] + public void When_subject_is_null_and_enum_has_some_value_it_should_throw() + { + //----------------------------------------------------------------------------------------------------------- + // Arrange + //----------------------------------------------------------------------------------------------------------- + object subject = null; + object expectedEnum = EnumULong.UInt64Max; + + //----------------------------------------------------------------------------------------------------------- + // Act + //----------------------------------------------------------------------------------------------------------- + // ReSharper disable once ExpressionIsAlwaysNull + Action act = () => subject.Should().BeEquivalentTo(expectedEnum, x => x.ComparingEnumsByName(), "comparing enums should throw"); + + //----------------------------------------------------------------------------------------------------------- + // Assert + //----------------------------------------------------------------------------------------------------------- + act.Should().Throw() + .WithMessage($"Expected*to equal EnumULong.UInt64Max({(UInt64)EnumULong.UInt64Max}) by name because comparing enums should throw, but found null*"); + } + + [Fact] + public void When_expectation_is_null_and_subject_enum_has_some_value_it_should_throw() + { + //----------------------------------------------------------------------------------------------------------- + // Arrange + //----------------------------------------------------------------------------------------------------------- + object subjectEnum = EnumULong.UInt64Max; + object expected = null; + + //----------------------------------------------------------------------------------------------------------- + // Act + //----------------------------------------------------------------------------------------------------------- + // ReSharper disable once ExpressionIsAlwaysNull + Action act = () => subjectEnum.Should().BeEquivalentTo(expected, x => x.ComparingEnumsByName(), "comparing enums should throw"); + //----------------------------------------------------------------------------------------------------------- // Assert - act.Should().Throw(); + //----------------------------------------------------------------------------------------------------------- + act.Should().Throw() + .WithMessage("Expected*to be , but found UInt64Max*"); } } }