diff --git a/Src/FluentAssertions/Common/TypeMemberReflector.cs b/Src/FluentAssertions/Common/TypeMemberReflector.cs index ea65969cdf..3a5602feb2 100644 --- a/Src/FluentAssertions/Common/TypeMemberReflector.cs +++ b/Src/FluentAssertions/Common/TypeMemberReflector.cs @@ -106,7 +106,7 @@ private static List GetMembersFromHierarchy( return GetInterfaceMembers(typeToReflect, getMembers); } - return GetClassMembers(typeToReflect, getMembers); + return GetClassMembers(typeToReflect, getMembers).Union(GetInterfaceMembers(typeToReflect, getMembers)).ToList(); } private static List GetInterfaceMembers(Type typeToReflect, @@ -161,7 +161,7 @@ private static List GetClassMembers(Type typeToReflect } } - typeToReflect = typeToReflect.BaseType; + typeToReflect = typeToReflect.BaseType != typeof(object) ? typeToReflect.BaseType : null; } return members; diff --git a/Tests/FluentAssertions.Equivalency.Specs/FluentAssertions.Equivalency.Specs.csproj b/Tests/FluentAssertions.Equivalency.Specs/FluentAssertions.Equivalency.Specs.csproj index 92f192b555..834982361b 100644 --- a/Tests/FluentAssertions.Equivalency.Specs/FluentAssertions.Equivalency.Specs.csproj +++ b/Tests/FluentAssertions.Equivalency.Specs/FluentAssertions.Equivalency.Specs.csproj @@ -7,6 +7,7 @@ false $(NoWarn),IDE0052,1573,1591,1712 full + 12 diff --git a/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.cs index 0b02f9b911..0d799229b4 100644 --- a/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.cs +++ b/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.cs @@ -5,7 +5,6 @@ using System.Globalization; using System.Linq; using FluentAssertions.Common; -using JetBrains.Annotations; using Xunit; using Xunit.Sdk; @@ -15,64 +14,6 @@ public class SelectionRulesSpecs { public class Basic { - [Fact] - public void Property_names_are_case_sensitive() - { - // Arrange - var subject = new - { - Name = "John" - }; - - var other = new - { - name = "John" - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(other); - - // Assert - act.Should().Throw().WithMessage( - "Expectation*subject.name**other*not have*"); - } - - [Fact] - public void Field_names_are_case_sensitive() - { - // Arrange - var subject = new ClassWithFieldInUpperCase - { - Name = "John" - }; - - var other = new ClassWithFieldInLowerCase - { - name = "John" - }; - - // Act - Action act = () => subject.Should().BeEquivalentTo(other); - - // Assert - act.Should().Throw().WithMessage( - "Expectation*subject.name**other*not have*"); - } - - private class ClassWithFieldInLowerCase - { - [UsedImplicitly] -#pragma warning disable SA1307 - public string name; -#pragma warning restore SA1307 - } - - private class ClassWithFieldInUpperCase - { - [UsedImplicitly] - public string Name; - } - [Fact] public void When_a_property_is_an_indexer_it_should_be_ignored() { @@ -96,7 +37,6 @@ public void When_a_property_is_an_indexer_it_should_be_ignored() public class ClassWithIndexer { - [UsedImplicitly] public object Foo { get; set; } public string this[int n] => @@ -291,10 +231,8 @@ public void A_nested_class_without_properties_inside_a_collection_is_fine() internal class BaseClassPointingToClassWithoutProperties { - [UsedImplicitly] public string Name { get; set; } - [UsedImplicitly] public ClassWithoutProperty ClassWithoutProperty { get; } = new(); } @@ -457,7 +395,6 @@ private enum LocalType : byte public class CustomType { - [UsedImplicitly] public string Name { get; set; } } @@ -910,14 +847,13 @@ public void When_members_are_excluded_by_the_access_modifier_of_the_getter_using "internal", "protected-internal", "private", "private-protected"); var expected = new ClassWithAllAccessModifiersForMembers("public", "protected", - "ignored-internal", "ignored-protected-internal", "private", "private-protected"); + "ignored-internal", "ignored-protected-internal", "private", "ignore-private-protected"); // Act - Action act = () => subject.Should().BeEquivalentTo(expected, config => config - .IncludingInternalFields() - .Excluding(ctx => - ctx.WhichGetterHas(CSharpAccessModifier.Internal) || - ctx.WhichGetterHas(CSharpAccessModifier.ProtectedInternal))); + Action act = () => subject.Should().BeEquivalentTo(expected, config => + config.Excluding(ctx => ctx.WhichGetterHas(CSharpAccessModifier.Internal) || + ctx.WhichGetterHas(CSharpAccessModifier.ProtectedInternal) || + ctx.WhichGetterHas(CSharpAccessModifier.PrivateProtected))); // Assert act.Should().NotThrow(); @@ -931,15 +867,14 @@ public void When_members_are_excluded_by_the_access_modifier_of_the_setter_using "internal", "protected-internal", "private", "private-protected"); var expected = new ClassWithAllAccessModifiersForMembers("public", "protected", - "ignored-internal", "ignored-protected-internal", "ignored-private", "private-protected"); + "ignored-internal", "ignored-protected-internal", "ignored-private", "ignore-private-protected"); // Act - Action act = () => subject.Should().BeEquivalentTo(expected, config => config - .IncludingInternalFields() - .Excluding(ctx => - ctx.WhichSetterHas(CSharpAccessModifier.Internal) || + Action act = () => subject.Should().BeEquivalentTo(expected, config => + config.Excluding(ctx => ctx.WhichSetterHas(CSharpAccessModifier.Internal) || ctx.WhichSetterHas(CSharpAccessModifier.ProtectedInternal) || - ctx.WhichSetterHas(CSharpAccessModifier.Private))); + ctx.WhichSetterHas(CSharpAccessModifier.Private) || + ctx.WhichSetterHas(CSharpAccessModifier.PrivateProtected))); // Assert act.Should().NotThrow(); @@ -1365,13 +1300,10 @@ public void When_a_property_is_internal_and_it_should_be_included_it_should_fail private class ClassWithInternalProperty { - [UsedImplicitly] public string PublicProperty { get; set; } - [UsedImplicitly] internal string InternalProperty { get; set; } - [UsedImplicitly] protected internal string ProtectedInternalProperty { get; set; } } @@ -1424,13 +1356,10 @@ public void When_a_field_is_internal_and_it_should_be_included_it_should_fail_th private class ClassWithInternalField { - [UsedImplicitly] public string PublicField; - [UsedImplicitly] internal string InternalField; - [UsedImplicitly] protected internal string ProtectedInternalField; } @@ -1455,58 +1384,6 @@ public void When_a_property_is_internal_it_should_be_excluded_from_the_compariso // Act / Assert actual.Should().BeEquivalentTo(expected); } - - [Fact] - public void Private_protected_properties_are_ignored() - { - // Arrange - var subject = new ClassWithPrivateProtectedProperty("Name", 13); - var other = new ClassWithPrivateProtectedProperty("Name", 37); - - // Act/Assert - subject.Should().BeEquivalentTo(other); - } - - private class ClassWithPrivateProtectedProperty - { - public ClassWithPrivateProtectedProperty(string name, int value) - { - Name = name; - Value = value; - } - - [UsedImplicitly] - public string Name { get; } - - [UsedImplicitly] - private protected int Value { get; } - } - - [Fact] - public void Private_protected_fields_are_ignored() - { - // Arrange - var subject = new ClassWithPrivateProtectedField("Name", 13); - var other = new ClassWithPrivateProtectedField("Name", 37); - - // Act/Assert - subject.Should().BeEquivalentTo(other); - } - - private class ClassWithPrivateProtectedField - { - public ClassWithPrivateProtectedField(string name, int value) - { - Name = name; - this.value = value; - } - - [UsedImplicitly] - public string Name; - - [UsedImplicitly] - private protected int value; - } } public class MemberHiding @@ -1580,6 +1457,35 @@ public void Includes_hidden_property_of_the_base_when_using_a_reference_to_the_b subject.Should().BeEquivalentTo(expectation); } +#if NET6_0_OR_GREATER + + [Fact] + [SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1010:Opening square brackets should be spaced correctly")] + [SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1011:Closing square brackets should be spaced correctly")] + public void BeEquivalentTo_Default_Interface_Method() + { + List lista = [ new Test { Name = "Test" } ]; + List listb = [ new Test { Name = "Test" } ]; + + lista.Should() + .BeEquivalentTo( + listb); // Fails with: Expectation has property lista[0].NameLength that the other object does not have. + } + + public class Test : ITest + { + public string Name { get; set; } + } + + public interface ITest + { + public string Name { get; } + + public int NameLength => Name.Length; + } + +#endif + [Fact] public void Run_type_typing_ignores_hidden_properties_even_when_using_a_reference_to_the_base_class() { @@ -1647,31 +1553,26 @@ public void Excluding_the_property_hiding_the_base_class_one_does_not_reveal_the private class BaseWithProperty { - [UsedImplicitly] public object Property { get; set; } } private class SubclassAHidingProperty : BaseWithProperty { - [UsedImplicitly] public new T Property { get; set; } } private class BaseWithStringProperty { - [UsedImplicitly] public string Property { get; set; } } private class SubclassHidingStringProperty : BaseWithStringProperty { - [UsedImplicitly] public new string Property { get; set; } } private class AnotherBaseWithProperty { - [UsedImplicitly] public object Property { get; set; } } @@ -1800,25 +1701,21 @@ public void Excluding_the_field_hiding_the_base_class_one_does_not_reveal_the_la private class BaseWithField { - [UsedImplicitly] public string Field; } private class SubclassAHidingField : BaseWithField { - [UsedImplicitly] public new string Field; } private class AnotherBaseWithField { - [UsedImplicitly] public string Field; } private class SubclassBHidingField : AnotherBaseWithField { - [UsedImplicitly] public new string Field; } } @@ -1895,7 +1792,7 @@ public void When_a_reference_to_an_explicit_interface_impl_is_provided_it_should } [Fact] - public void Explicitly_implemented_subject_properties_are_ignored_if_a_normal_property_exists_with_the_same_name() + public void When_respecting_declared_types_explicit_interface_member_on_interfaced_subject_should_be_used() { // Arrange IVehicle expected = new Vehicle @@ -1905,85 +1802,43 @@ public void Explicitly_implemented_subject_properties_are_ignored_if_a_normal_pr IVehicle subject = new ExplicitVehicle { - VehicleId = 2 // normal property + VehicleId = 2 // instance member }; - subject.VehicleId = 1; // explicitly implemented property + subject.VehicleId = 1; // interface member // Act - Action action = () => subject.Should().BeEquivalentTo(expected); + Action action = () => subject.Should().BeEquivalentTo(expected, opt => opt.RespectingDeclaredTypes()); // Assert - action.Should().Throw(); + action.Should().NotThrow(); } [Fact] - public void Explicitly_implemented_read_only_subject_properties_are_ignored_if_a_normal_property_exists_with_the_same_name() + public void When_respecting_declared_types_explicit_interface_member_on_interfaced_expectation_should_be_used() { // Arrange - IReadOnlyVehicle subject = new ExplicitReadOnlyVehicle(explicitValue: 1) - { - VehicleId = 2 // normal property - }; - - var expected = new Vehicle + IVehicle expected = new ExplicitVehicle { - VehicleId = 1 + VehicleId = 2 // instance member }; - // Act - Action action = () => subject.Should().BeEquivalentTo(expected); - - // Assert - action.Should().Throw(); - } - - [Fact] - public void Explicitly_implemented_subject_properties_are_ignored_if_only_fields_are_included() - { - // Arrange - var expected = new VehicleWithField - { - VehicleId = 1 // A field named like a property - }; + expected.VehicleId = 1; // interface member - var subject = new ExplicitVehicle + IVehicle subject = new Vehicle { - VehicleId = 2 // A real property + VehicleId = 1 }; // Act - Action action = () => subject.Should().BeEquivalentTo(expected, opt => opt - .IncludingFields() - .ExcludingProperties()); + Action action = () => subject.Should().BeEquivalentTo(expected, opt => opt.RespectingDeclaredTypes()); // Assert - action.Should().Throw().WithMessage("*field*VehicleId*other*"); - } - - [Fact] - public void Explicitly_implemented_subject_properties_are_ignored_if_only_fields_are_included_and_they_may_be_missing() - { - // Arrange - var expected = new VehicleWithField - { - VehicleId = 1 // A field named like a property - }; - - var subject = new ExplicitVehicle - { - VehicleId = 2 // A real property - }; - - // Act / Assert - subject.Should().BeEquivalentTo(expected, opt => opt - .IncludingFields() - .ExcludingProperties() - .ExcludingMissingMembers()); + action.Should().NotThrow(); } [Fact] - public void Excluding_missing_members_does_not_affect_how_explicitly_implemented_subject_properties_are_dealt_with() + public void When_respecting_runtime_types_explicit_interface_member_on_interfaced_subject_should_not_be_used() { // Arrange IVehicle expected = new Vehicle @@ -1999,14 +1854,14 @@ public void Excluding_missing_members_does_not_affect_how_explicitly_implemented subject.VehicleId = 1; // interface member // Act - Action action = () => subject.Should().BeEquivalentTo(expected, opt => opt.ExcludingMissingMembers()); + Action action = () => subject.Should().BeEquivalentTo(expected, opt => opt.RespectingRuntimeTypes()); // Assert action.Should().Throw(); } [Fact] - public void When_respecting_declared_types_explicit_interface_member_on_interfaced_expectation_should_be_used() + public void When_respecting_runtime_types_explicit_interface_member_on_interfaced_expectation_should_not_be_used() { // Arrange IVehicle expected = new ExplicitVehicle @@ -2022,100 +1877,102 @@ public void When_respecting_declared_types_explicit_interface_member_on_interfac }; // Act - Action action = () => subject.Should().BeEquivalentTo(expected, opt => opt.RespectingDeclaredTypes()); + Action action = () => subject.Should().BeEquivalentTo(expected, opt => opt.RespectingRuntimeTypes()); // Assert - action.Should().NotThrow(); + action.Should().Throw(); } [Fact] - public void When_respecting_runtime_types_explicit_interface_member_on_interfaced_subject_should_not_be_used() + public void When_respecting_declared_types_explicit_interface_member_on_subject_should_not_be_used() { // Arrange - IVehicle expected = new Vehicle + var expected = new Vehicle { VehicleId = 1 }; - IVehicle subject = new ExplicitVehicle + var subject = new ExplicitVehicle { - VehicleId = 2 // instance member + VehicleId = 2 }; - subject.VehicleId = 1; // interface member + ((IVehicle)subject).VehicleId = 1; // Act - Action action = () => subject.Should().BeEquivalentTo(expected, opt => opt.RespectingRuntimeTypes()); + Action action = () => subject.Should().BeEquivalentTo(expected, opt => opt.RespectingDeclaredTypes()); // Assert action.Should().Throw(); } [Fact] - public void When_respecting_runtime_types_explicit_interface_member_on_interfaced_expectation_should_not_be_used() + public void When_respecting_declared_types_explicit_interface_member_on_expectation_should_not_be_used() { // Arrange - IVehicle expected = new ExplicitVehicle + var expected = new ExplicitVehicle { - VehicleId = 2 // instance member + VehicleId = 2 }; - expected.VehicleId = 1; // interface member + ((IVehicle)expected).VehicleId = 1; - IVehicle subject = new Vehicle + var subject = new Vehicle { VehicleId = 1 }; // Act - Action action = () => subject.Should().BeEquivalentTo(expected, opt => opt.RespectingRuntimeTypes()); + Action action = () => subject.Should().BeEquivalentTo(expected, opt => opt.RespectingDeclaredTypes()); // Assert action.Should().Throw(); } [Fact] - public void When_respecting_declared_types_explicit_interface_member_on_expectation_should_not_be_used() + public void When_respecting_runtime_types_explicit_interface_member_on_subject_should_not_be_used() { // Arrange - var expected = new ExplicitVehicle + var expected = new Vehicle { - VehicleId = 2 + VehicleId = 1 }; - ((IVehicle)expected).VehicleId = 1; - - var subject = new Vehicle + var subject = new ExplicitVehicle { - VehicleId = 1 + VehicleId = 2 }; + ((IVehicle)subject).VehicleId = 1; + // Act - Action action = () => subject.Should().BeEquivalentTo(expected); + Action action = () => subject.Should().BeEquivalentTo(expected, opt => opt.RespectingRuntimeTypes()); // Assert action.Should().Throw(); } [Fact] - public void Can_find_explicitly_implemented_property_on_the_subject() + public void When_respecting_runtime_types_explicit_interface_member_on_expectation_should_not_be_used() { // Arrange - IPerson person = new Person(); - person.Name = "Bob"; + var expected = new ExplicitVehicle + { + VehicleId = 2 + }; - // Act / Assert - person.Should().BeEquivalentTo(new { Name = "Bob" }); - } + ((IVehicle)expected).VehicleId = 1; - private interface IPerson - { - string Name { get; set; } - } + var subject = new Vehicle + { + VehicleId = 1 + }; - private class Person : IPerson - { - string IPerson.Name { get; set; } + // Act + Action action = () => subject.Should().BeEquivalentTo(expected, opt => opt.RespectingRuntimeTypes()); + + // Assert + action.Should().Throw(); } [Fact] @@ -2257,13 +2114,11 @@ public void Excluding_a_covariant_property_through_the_base_class_excludes_the_b private class BaseWithProperty { - [UsedImplicitly] public string BaseProperty { get; set; } } private class DerivedWithProperty : BaseWithProperty { - [UsedImplicitly] public string DerivedProperty { get; set; } } @@ -2276,7 +2131,6 @@ private sealed class DerivedWithCovariantOverride : BaseWithAbstractProperty { public override DerivedWithProperty Property { get; } - [UsedImplicitly] public string OtherProp { get; set; } public DerivedWithCovariantOverride(DerivedWithProperty prop) @@ -2824,59 +2678,45 @@ public void private class ClassWithNonBrowsableMembers { - [UsedImplicitly] public int BrowsableField = -1; - [UsedImplicitly] public int BrowsableProperty { get; set; } - [UsedImplicitly] [EditorBrowsable(EditorBrowsableState.Always)] public int ExplicitlyBrowsableField = -1; - [UsedImplicitly] [EditorBrowsable(EditorBrowsableState.Always)] public int ExplicitlyBrowsableProperty { get; set; } - [UsedImplicitly] [EditorBrowsable(EditorBrowsableState.Advanced)] public int AdvancedBrowsableField = -1; - [UsedImplicitly] [EditorBrowsable(EditorBrowsableState.Advanced)] public int AdvancedBrowsableProperty { get; set; } - [UsedImplicitly] [EditorBrowsable(EditorBrowsableState.Never)] public int NonBrowsableField = -1; - [UsedImplicitly] [EditorBrowsable(EditorBrowsableState.Never)] public int NonBrowsableProperty { get; set; } } private class ClassWhereMemberThatCouldBeNonBrowsableIsBrowsable { - [UsedImplicitly] public int BrowsableProperty { get; set; } - [UsedImplicitly] public int FieldThatMightBeNonBrowsable = -1; - [UsedImplicitly] public int PropertyThatMightBeNonBrowsable { get; set; } } private class ClassWhereMemberThatCouldBeNonBrowsableIsNonBrowsable { - [UsedImplicitly] public int BrowsableProperty { get; set; } - [UsedImplicitly] [EditorBrowsable(EditorBrowsableState.Never)] public int FieldThatMightBeNonBrowsable = -1; - [UsedImplicitly] [EditorBrowsable(EditorBrowsableState.Never)] public int PropertyThatMightBeNonBrowsable { get; set; } }