From 4b93077916655f7150c47600f1b9c2f38dbbe8d3 Mon Sep 17 00:00:00 2001 From: Dennis Doomen Date: Thu, 28 Nov 2024 19:39:12 +0100 Subject: [PATCH] SAVEPOINT --- FluentAssertions.sln.DotSettings | 1 + Src/FluentAssertions/Common/MemberPath.cs | 2 +- Src/FluentAssertions/Common/TypeExtensions.cs | 2 +- .../Equivalency/AssertionChainExtensions.cs | 2 +- .../EquivalencyValidationContext.cs | 4 +- .../Equivalency/EquivalencyValidator.cs | 2 +- .../Equivalency/Execution/ObjectInfo.cs | 2 +- Src/FluentAssertions/Equivalency/Field.cs | 15 +- Src/FluentAssertions/Equivalency/INode.cs | 32 +--- .../Matching/MappedMemberMatchingRule.cs | 2 +- .../Matching/MappedPathMatchingRule.cs | 2 +- .../Matching/MustMatchByNameRule.cs | 8 +- .../Matching/TryMatchByNameRule.cs | 4 +- Src/FluentAssertions/Equivalency/Node.cs | 58 +++---- Src/FluentAssertions/Equivalency/Pathway.cs | 62 +++++++ Src/FluentAssertions/Equivalency/Property.cs | 6 +- .../Selection/MemberToMemberInfoAdapter.cs | 4 +- .../SelectMemberByPathSelectionRule.cs | 2 +- .../Steps/AssertionRuleEquivalencyStep.cs | 6 +- .../Equivalency/Steps/AutoConversionStep.cs | 4 +- .../Steps/DictionaryEquivalencyStep.cs | 4 +- .../Steps/EnumerableEquivalencyValidator.cs | 14 +- .../Steps/StringEqualityEquivalencyStep.cs | 2 +- .../StructuralEqualityEquivalencyStep.cs | 5 +- .../Steps/ValueTypeEquivalencyStep.cs | 2 +- .../FluentAssertions/net47.verified.txt | 18 +- .../FluentAssertions/net6.0.verified.txt | 18 +- .../netstandard2.0.verified.txt | 18 +- .../netstandard2.1.verified.txt | 18 +- .../BasicSpecs.cs | 4 +- .../ExtensibilitySpecs.cs | 4 +- .../MemberLessObjectsSpecs.cs | 2 +- .../MemberMatchingSpecs.cs | 163 +++++++++++++++++- .../NestedPropertiesSpecs.cs | 2 +- .../SelectionRulesSpecs.Basic.cs | 6 +- .../SelectionRulesSpecs.Browsability.cs | 2 +- .../SelectionRulesSpecs.cs | 2 +- .../Execution/CallerIdentificationSpecs.cs | 2 +- .../Numeric/ComparableSpecs.cs | 2 +- 39 files changed, 364 insertions(+), 144 deletions(-) create mode 100644 Src/FluentAssertions/Equivalency/Pathway.cs diff --git a/FluentAssertions.sln.DotSettings b/FluentAssertions.sln.DotSettings index 57338f40c4..63be72f08a 100644 --- a/FluentAssertions.sln.DotSettings +++ b/FluentAssertions.sln.DotSettings @@ -145,6 +145,7 @@ <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> OUTLINE + Minimal SOLUTION_FOLDER True True diff --git a/Src/FluentAssertions/Common/MemberPath.cs b/Src/FluentAssertions/Common/MemberPath.cs index 77cf3a3c20..b77296ea73 100644 --- a/Src/FluentAssertions/Common/MemberPath.cs +++ b/Src/FluentAssertions/Common/MemberPath.cs @@ -20,7 +20,7 @@ internal class MemberPath private static readonly MemberPathSegmentEqualityComparer MemberPathSegmentEqualityComparer = new(); public MemberPath(IMember member, string parentPath) - : this(member.ReflectedType, member.DeclaringType, parentPath.Combine(member.Name)) + : this(member.ReflectedType, member.DeclaringType, parentPath.Combine(member.Expectation.Name ?? member.Subject.Name)) { } diff --git a/Src/FluentAssertions/Common/TypeExtensions.cs b/Src/FluentAssertions/Common/TypeExtensions.cs index 966bf39bf6..3542b499cc 100644 --- a/Src/FluentAssertions/Common/TypeExtensions.cs +++ b/Src/FluentAssertions/Common/TypeExtensions.cs @@ -138,7 +138,7 @@ public static bool IsEquivalentTo(this IMember property, IMember otherProperty) { return (property.DeclaringType.IsSameOrInherits(otherProperty.DeclaringType) || otherProperty.DeclaringType.IsSameOrInherits(property.DeclaringType)) && - property.Name == otherProperty.Name; + property.Subject.Name == otherProperty.Subject.Name; } /// diff --git a/Src/FluentAssertions/Equivalency/AssertionChainExtensions.cs b/Src/FluentAssertions/Equivalency/AssertionChainExtensions.cs index 95227db2cb..d7cfd1e1e5 100644 --- a/Src/FluentAssertions/Equivalency/AssertionChainExtensions.cs +++ b/Src/FluentAssertions/Equivalency/AssertionChainExtensions.cs @@ -10,7 +10,7 @@ internal static class AssertionChainExtensions /// public static AssertionChain For(this AssertionChain chain, IEquivalencyValidationContext context) { - chain.OverrideCallerIdentifier(() => context.CurrentNode.Description); + chain.OverrideCallerIdentifier(() => context.CurrentNode.Subject.Description); return chain .WithReportable("configuration", () => context.Options.ToString()) diff --git a/Src/FluentAssertions/Equivalency/EquivalencyValidationContext.cs b/Src/FluentAssertions/Equivalency/EquivalencyValidationContext.cs index 2fbced998e..f596364fa8 100644 --- a/Src/FluentAssertions/Equivalency/EquivalencyValidationContext.cs +++ b/Src/FluentAssertions/Equivalency/EquivalencyValidationContext.cs @@ -74,7 +74,7 @@ public bool IsCyclicReference(object expectation) bool compareByMembers = expectation is not null && Options.GetEqualityStrategy(expectation.GetType()) is EqualityStrategy.Members or EqualityStrategy.ForceMembers; - var reference = new ObjectReference(expectation, CurrentNode.PathAndName, compareByMembers); + var reference = new ObjectReference(expectation, CurrentNode.Subject.PathAndName, compareByMembers); return CyclicReferenceDetector.IsCyclicReference(reference); } @@ -82,6 +82,6 @@ public bool IsCyclicReference(object expectation) public override string ToString() { - return Invariant($"{{Path=\"{CurrentNode.Description}\"}}"); + return Invariant($"{{Path=\"{CurrentNode.Subject.PathAndName}\"}}"); } } diff --git a/Src/FluentAssertions/Equivalency/EquivalencyValidator.cs b/Src/FluentAssertions/Equivalency/EquivalencyValidator.cs index e79505f608..2637fb2f46 100644 --- a/Src/FluentAssertions/Equivalency/EquivalencyValidator.cs +++ b/Src/FluentAssertions/Equivalency/EquivalencyValidator.cs @@ -90,7 +90,7 @@ private static void AssertEquivalencyForCyclicReference(Comparands comparands, A private void TryToProveNodesAreEquivalent(Comparands comparands, IEquivalencyValidationContext context) { - using var _ = context.Tracer.WriteBlock(node => node.Description); + using var _ = context.Tracer.WriteBlock(node => node.Subject.Description); foreach (IEquivalencyStep step in AssertionOptions.EquivalencyPlan) { diff --git a/Src/FluentAssertions/Equivalency/Execution/ObjectInfo.cs b/Src/FluentAssertions/Equivalency/Execution/ObjectInfo.cs index 0f45024f21..345e572a05 100644 --- a/Src/FluentAssertions/Equivalency/Execution/ObjectInfo.cs +++ b/Src/FluentAssertions/Equivalency/Execution/ObjectInfo.cs @@ -8,7 +8,7 @@ public ObjectInfo(Comparands comparands, INode currentNode) { Type = currentNode.Type; ParentType = currentNode.ParentType; - Path = currentNode.PathAndName; + Path = currentNode.Expectation.PathAndName; CompileTimeType = comparands.CompileTimeType; RuntimeType = comparands.RuntimeType; } diff --git a/Src/FluentAssertions/Equivalency/Field.cs b/Src/FluentAssertions/Equivalency/Field.cs index 175a78d677..fc3d00a2fd 100644 --- a/Src/FluentAssertions/Equivalency/Field.cs +++ b/Src/FluentAssertions/Equivalency/Field.cs @@ -6,7 +6,7 @@ namespace FluentAssertions.Equivalency; /// -/// A specialized type of that represents a field of an object in a structural equivalency assertion. +/// A specialized type of that represents a field of an object in a structural equivalency assertion. /// internal class Field : Node, IMember { @@ -14,18 +14,13 @@ internal class Field : Node, IMember private bool? isBrowsable; public Field(FieldInfo fieldInfo, INode parent) - : this(fieldInfo.ReflectedType, fieldInfo, parent) - { - } - - public Field(Type reflectedType, FieldInfo fieldInfo, INode parent) { this.fieldInfo = fieldInfo; DeclaringType = fieldInfo.DeclaringType; - ReflectedType = reflectedType; - Path = parent.PathAndName; + ReflectedType = fieldInfo.ReflectedType; + Subject = new Pathway(parent.Subject.PathAndName, fieldInfo.Name, pathAndName => $"field {parent.GetSubjectId().Combine(pathAndName)}"); + Expectation = new Pathway(parent.Expectation.PathAndName, fieldInfo.Name, pathAndName => $"field {pathAndName}"); GetSubjectId = parent.GetSubjectId; - Name = fieldInfo.Name; Type = fieldInfo.FieldType; ParentType = fieldInfo.DeclaringType; RootIsCollection = parent.RootIsCollection; @@ -40,8 +35,6 @@ public object GetValue(object obj) public Type DeclaringType { get; set; } - public override string Description => $"field {GetSubjectId().Combine(PathAndName)}"; - public CSharpAccessModifier GetterAccessibility => fieldInfo.GetCSharpAccessModifier(); public CSharpAccessModifier SetterAccessibility => fieldInfo.GetCSharpAccessModifier(); diff --git a/Src/FluentAssertions/Equivalency/INode.cs b/Src/FluentAssertions/Equivalency/INode.cs index 29274315a0..421e2a238d 100644 --- a/Src/FluentAssertions/Equivalency/INode.cs +++ b/Src/FluentAssertions/Equivalency/INode.cs @@ -1,5 +1,4 @@ using System; -using JetBrains.Annotations; namespace FluentAssertions.Equivalency; @@ -15,14 +14,6 @@ public interface INode /// GetSubjectId GetSubjectId { get; } - /// - /// Gets the name of this node. - /// - /// - /// "Property2" - /// - string Name { get; set; } - /// /// Gets the type of this node, e.g. the type of the field or property, or the type of the collection item. /// @@ -34,24 +25,17 @@ public interface INode /// /// Is for the root object. /// - [CanBeNull] Type ParentType { get; } /// - /// Gets the path from the root object UNTIL the current node, separated by dots or index/key brackets. + /// Gets the path from the root of the subject upto and including to the current node. /// - /// - /// "Parent[0].Property2" - /// - string Path { get; } + Pathway Subject { get; set; } /// - /// Gets the full path from the root object up to and including the name of the node. + /// Gets the path from the root of the expectation upto and including to the current node. /// - /// - /// "Parent[0]" - /// - string PathAndName { get; } + Pathway Expectation { get; } /// /// Gets a zero-based number representing the depth within the object graph @@ -62,14 +46,6 @@ public interface INode /// int Depth { get; } - /// - /// Gets the path including the description of the subject. - /// - /// - /// "property subject.Parent[0].Property2" - /// - string Description { get; } - /// /// Gets a value indicating whether the current node is the root. /// diff --git a/Src/FluentAssertions/Equivalency/Matching/MappedMemberMatchingRule.cs b/Src/FluentAssertions/Equivalency/Matching/MappedMemberMatchingRule.cs index faa1aaaea2..0eace99527 100644 --- a/Src/FluentAssertions/Equivalency/Matching/MappedMemberMatchingRule.cs +++ b/Src/FluentAssertions/Equivalency/Matching/MappedMemberMatchingRule.cs @@ -33,7 +33,7 @@ public MappedMemberMatchingRule(string expectationMemberName, string subjectMemb public IMember Match(IMember expectedMember, object subject, INode parent, IEquivalencyOptions options, AssertionChain assertionChain) { if (parent.Type.IsSameOrInherits(typeof(TExpectation)) && subject is TSubject && - expectedMember.Name == expectationMemberName) + expectedMember.Subject.Name == expectationMemberName) { var member = MemberFactory.Find(subject, subjectMemberName, parent); diff --git a/Src/FluentAssertions/Equivalency/Matching/MappedPathMatchingRule.cs b/Src/FluentAssertions/Equivalency/Matching/MappedPathMatchingRule.cs index 88c44a8f4c..33d0fa8bdc 100644 --- a/Src/FluentAssertions/Equivalency/Matching/MappedPathMatchingRule.cs +++ b/Src/FluentAssertions/Equivalency/Matching/MappedPathMatchingRule.cs @@ -52,7 +52,7 @@ public IMember Match(IMember expectedMember, object subject, INode parent, IEqui path = path.WithCollectionAsRoot(); } - if (path.IsEquivalentTo(expectedMember.PathAndName)) + if (path.IsEquivalentTo(expectedMember.Subject.PathAndName)) { var member = MemberFactory.Find(subject, subjectPath.MemberName, parent); diff --git a/Src/FluentAssertions/Equivalency/Matching/MustMatchByNameRule.cs b/Src/FluentAssertions/Equivalency/Matching/MustMatchByNameRule.cs index 44715cb8de..e34bfa5076 100644 --- a/Src/FluentAssertions/Equivalency/Matching/MustMatchByNameRule.cs +++ b/Src/FluentAssertions/Equivalency/Matching/MustMatchByNameRule.cs @@ -16,7 +16,7 @@ public IMember Match(IMember expectedMember, object subject, INode parent, IEqui if (options.IncludedProperties != MemberVisibility.None) { PropertyInfo propertyInfo = subject.GetType().FindProperty( - expectedMember.Name, + expectedMember.Subject.Name, options.IncludedProperties | MemberVisibility.ExplicitlyImplemented | MemberVisibility.DefaultInterfaceProperties); subjectMember = propertyInfo is not null && !propertyInfo.IsIndexer() ? new Property(propertyInfo, parent) : null; @@ -25,7 +25,7 @@ public IMember Match(IMember expectedMember, object subject, INode parent, IEqui if (subjectMember is null && options.IncludedFields != MemberVisibility.None) { FieldInfo fieldInfo = subject.GetType().FindField( - expectedMember.Name, + expectedMember.Subject.Name, options.IncludedFields); subjectMember = fieldInfo is not null ? new Field(fieldInfo, parent) : null; @@ -34,12 +34,12 @@ public IMember Match(IMember expectedMember, object subject, INode parent, IEqui if (subjectMember is null) { assertionChain.FailWith( - $"Expectation has {expectedMember.Description} that the other object does not have."); + $"Expectation has {expectedMember.Expectation} that the other object does not have."); } else if (options.IgnoreNonBrowsableOnSubject && !subjectMember.IsBrowsable) { assertionChain.FailWith( - $"Expectation has {expectedMember.Description} that is non-browsable in the other object, and non-browsable " + + $"Expectation has {expectedMember.Expectation} that is non-browsable in the other object, and non-browsable " + "members on the subject are ignored with the current configuration"); } else diff --git a/Src/FluentAssertions/Equivalency/Matching/TryMatchByNameRule.cs b/Src/FluentAssertions/Equivalency/Matching/TryMatchByNameRule.cs index 9d6d7dc95c..89db121b79 100644 --- a/Src/FluentAssertions/Equivalency/Matching/TryMatchByNameRule.cs +++ b/Src/FluentAssertions/Equivalency/Matching/TryMatchByNameRule.cs @@ -13,7 +13,7 @@ public IMember Match(IMember expectedMember, object subject, INode parent, IEqui { if (options.IncludedProperties != MemberVisibility.None) { - PropertyInfo property = subject.GetType().FindProperty(expectedMember.Name, + PropertyInfo property = subject.GetType().FindProperty(expectedMember.Subject.Name, options.IncludedProperties | MemberVisibility.ExplicitlyImplemented); if (property is not null && !property.IsIndexer()) @@ -23,7 +23,7 @@ public IMember Match(IMember expectedMember, object subject, INode parent, IEqui } FieldInfo field = subject.GetType() - .FindField(expectedMember.Name, options.IncludedFields); + .FindField(expectedMember.Subject.Name, options.IncludedFields); return field is not null ? new Field(field, parent) : null; } diff --git a/Src/FluentAssertions/Equivalency/Node.cs b/Src/FluentAssertions/Equivalency/Node.cs index 96ee51e87f..10a1d063a0 100644 --- a/Src/FluentAssertions/Equivalency/Node.cs +++ b/Src/FluentAssertions/Equivalency/Node.cs @@ -12,10 +12,8 @@ internal class Node : INode private GetSubjectId subjectIdProvider; - private string path; - private string name; - private string pathAndName; private string cachedSubjectId; + private Pathway subject; public GetSubjectId GetSubjectId { @@ -27,29 +25,20 @@ public GetSubjectId GetSubjectId public Type ParentType { get; protected set; } - public string Path + public Pathway Subject { - get => path; - protected set - { - path = value; - pathAndName = null; - } - } - - public string PathAndName => pathAndName ??= Path.Combine(Name); - - public string Name - { - get => name; + get => subject; set { - name = value; - pathAndName = null; + subject = value; + if (Expectation is null) + { + Expectation = new Pathway(value); + } } } - public virtual string Description => $"{GetSubjectId().Combine(PathAndName)}"; + public Pathway Expectation { get; protected set; } public bool IsRoot { @@ -57,11 +46,11 @@ public bool IsRoot { // If the root is a collection, we need treat the objects in that collection as the root of the graph because all options // refer to the type of the collection items. - return PathAndName.Length == 0 || (RootIsCollection && IsFirstIndex); + return Subject.PathAndName.Length == 0 || (RootIsCollection && IsFirstIndex); } } - private bool IsFirstIndex => MatchFirstIndex.IsMatch(PathAndName); + private bool IsFirstIndex => MatchFirstIndex.IsMatch(Subject.PathAndName); public bool RootIsCollection { get; protected set; } @@ -70,7 +59,7 @@ public int Depth get { const char memberSeparator = '.'; - return PathAndName.Count(chr => chr == memberSeparator); + return Subject.PathAndName.Count(chr => chr == memberSeparator); } } @@ -84,8 +73,7 @@ public static INode From(GetSubjectId getSubjectId) return new Node { subjectIdProvider = () => getSubjectId() ?? "root", - Name = string.Empty, - Path = string.Empty, + Subject = new Pathway(string.Empty, string.Empty, _ => getSubjectId()), Type = typeof(T), ParentType = null, RootIsCollection = IsCollection(typeof(T)) @@ -94,12 +82,14 @@ public static INode From(GetSubjectId getSubjectId) public static INode FromCollectionItem(string index, INode parent) { + Pathway.GetDescription getDescription = pathAndName => $"{parent.GetSubjectId().Combine(pathAndName)}"; + return new Node { Type = typeof(T), ParentType = parent.Type, - Name = "[" + index + "]", - Path = parent.PathAndName, + Subject = new Pathway(parent.Subject, "[" + index + "]", getDescription), + Expectation = new Pathway(parent.Expectation, "[" + index + "]", getDescription), GetSubjectId = parent.GetSubjectId, RootIsCollection = parent.RootIsCollection }; @@ -107,12 +97,14 @@ public static INode FromCollectionItem(string index, INode parent) public static INode FromDictionaryItem(object key, INode parent) { + Pathway.GetDescription getDescription = pathAndName => $"{parent.GetSubjectId().Combine(pathAndName)}"; + return new Node { Type = typeof(T), ParentType = parent.Type, - Name = "[" + key + "]", - Path = parent.PathAndName, + Subject = new Pathway(parent.Subject, "[" + key + "]", getDescription), + Expectation = new Pathway(parent.Expectation, "[" + key + "]", getDescription), GetSubjectId = parent.GetSubjectId, RootIsCollection = parent.RootIsCollection }; @@ -138,7 +130,7 @@ public override bool Equals(object obj) return Equals((Node)obj); } - private bool Equals(Node other) => (Type, Name, Path) == (other.Type, other.Name, other.Path); + private bool Equals(Node other) => (Type, Subject.Name, Subject.Path) == (other.Type, other.Subject.Name, other.Subject.Path); public override int GetHashCode() { @@ -146,11 +138,11 @@ public override int GetHashCode() { #pragma warning disable CA1307 int hashCode = Type.GetHashCode(); - hashCode = (hashCode * 397) + Path.GetHashCode(); - hashCode = (hashCode * 397) + Name.GetHashCode(); + hashCode = (hashCode * 397) + Subject.Path.GetHashCode(); + hashCode = (hashCode * 397) + Subject.Name.GetHashCode(); return hashCode; } } - public override string ToString() => Description; + public override string ToString() => Subject.Description; } diff --git a/Src/FluentAssertions/Equivalency/Pathway.cs b/Src/FluentAssertions/Equivalency/Pathway.cs new file mode 100644 index 0000000000..39d1a2c6dd --- /dev/null +++ b/Src/FluentAssertions/Equivalency/Pathway.cs @@ -0,0 +1,62 @@ +using System; +using FluentAssertions.Common; + +namespace FluentAssertions.Equivalency; + +public record Pathway +{ + public delegate string GetDescription(string pathAndName); + + private readonly string path = string.Empty; + private readonly string name = string.Empty; + private string pathAndName; + + private readonly GetDescription getDescription; + + public Pathway(Pathway parent, string name, GetDescription getDescription) + { + Path = parent.PathAndName; + Name = name; + this.getDescription = getDescription; + } + + public Pathway(string path, string name, GetDescription getDescription) + { + Path = path; + Name = name; + this.getDescription = getDescription; + } + + public Pathway(Pathway original) + { + Path = original.path; + Name = original.name; + getDescription = original.getDescription; + } + + public string Path + { + get => path; + init + { + path = value; + pathAndName = null; + } + } + + public string Name + { + get => name; + init + { + name = value; + pathAndName = null; + } + } + + public string Description => getDescription(PathAndName); + + public string PathAndName => pathAndName ??= path.Combine(name); + + public override string ToString() => Description; +} diff --git a/Src/FluentAssertions/Equivalency/Property.cs b/Src/FluentAssertions/Equivalency/Property.cs index b198221339..33cce0a884 100644 --- a/Src/FluentAssertions/Equivalency/Property.cs +++ b/Src/FluentAssertions/Equivalency/Property.cs @@ -24,10 +24,10 @@ public Property(Type reflectedType, PropertyInfo propertyInfo, INode parent) ReflectedType = reflectedType; this.propertyInfo = propertyInfo; DeclaringType = propertyInfo.DeclaringType; - Name = propertyInfo.Name; + Subject = new Pathway(parent.Subject.PathAndName, propertyInfo.Name, pathAndName => $"property {parent.GetSubjectId().Combine(pathAndName)}"); + Expectation = new Pathway(parent.Expectation.PathAndName, propertyInfo.Name, pathAndName => $"property {pathAndName}"); Type = propertyInfo.PropertyType; ParentType = propertyInfo.DeclaringType; - Path = parent.PathAndName; GetSubjectId = parent.GetSubjectId; RootIsCollection = parent.RootIsCollection; } @@ -41,8 +41,6 @@ public object GetValue(object obj) public Type ReflectedType { get; } - public override string Description => $"property {GetSubjectId().Combine(PathAndName)}"; - public CSharpAccessModifier GetterAccessibility => propertyInfo.GetGetMethod(nonPublic: true).GetCSharpAccessModifier(); public CSharpAccessModifier SetterAccessibility => propertyInfo.GetSetMethod(nonPublic: true).GetCSharpAccessModifier(); diff --git a/Src/FluentAssertions/Equivalency/Selection/MemberToMemberInfoAdapter.cs b/Src/FluentAssertions/Equivalency/Selection/MemberToMemberInfoAdapter.cs index b855a5b69d..9c65d3cdeb 100644 --- a/Src/FluentAssertions/Equivalency/Selection/MemberToMemberInfoAdapter.cs +++ b/Src/FluentAssertions/Equivalency/Selection/MemberToMemberInfoAdapter.cs @@ -14,9 +14,9 @@ public MemberToMemberInfoAdapter(IMember member) { this.member = member; DeclaringType = member.DeclaringType; - Name = member.Name; + Name = member.Subject.Name; Type = member.Type; - Path = member.PathAndName; + Path = member.Subject.PathAndName; } public string Name { get; } diff --git a/Src/FluentAssertions/Equivalency/Selection/SelectMemberByPathSelectionRule.cs b/Src/FluentAssertions/Equivalency/Selection/SelectMemberByPathSelectionRule.cs index 77b9507bbb..ac37b31834 100644 --- a/Src/FluentAssertions/Equivalency/Selection/SelectMemberByPathSelectionRule.cs +++ b/Src/FluentAssertions/Equivalency/Selection/SelectMemberByPathSelectionRule.cs @@ -11,7 +11,7 @@ internal abstract class SelectMemberByPathSelectionRule : IMemberSelectionRule public IEnumerable SelectMembers(INode currentNode, IEnumerable selectedMembers, MemberSelectionContext context) { - var currentPath = RemoveRootIndexQualifier(currentNode.PathAndName); + var currentPath = RemoveRootIndexQualifier(currentNode.Expectation.PathAndName); var members = selectedMembers.ToList(); AddOrRemoveMembersFrom(members, currentNode, currentPath, context); diff --git a/Src/FluentAssertions/Equivalency/Steps/AssertionRuleEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/AssertionRuleEquivalencyStep.cs index 2a1990786b..08498cb7dc 100644 --- a/Src/FluentAssertions/Equivalency/Steps/AssertionRuleEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/AssertionRuleEquivalencyStep.cs @@ -72,12 +72,12 @@ private bool ExecuteAssertion(Comparands comparands, IEquivalencyValidationConte assertionChain .ForCondition(subjectIsNull || comparands.Subject.GetType().IsSameOrInherits(typeof(TSubject))) - .FailWith("Expected " + context.CurrentNode.Description + " from subject to be a {0}{reason}, but found a {1}.", + .FailWith("Expected " + context.CurrentNode.Subject + " from subject to be a {0}{reason}, but found a {1}.", typeof(TSubject), comparands.Subject?.GetType()) .Then .ForCondition(expectationIsNull || comparands.Expectation.GetType().IsSameOrInherits(typeof(TSubject))) .FailWith( - "Expected " + context.CurrentNode.Description + " from expectation to be a {0}{reason}, but found a {1}.", + "Expected " + context.CurrentNode.Subject + " from expectation to be a {0}{reason}, but found a {1}.", typeof(TSubject), comparands.Expectation?.GetType()); if (assertionChain.Succeeded) @@ -88,7 +88,7 @@ private bool ExecuteAssertion(Comparands comparands, IEquivalencyValidationConte } // Caller identitification should not get confused about invoking a Should within the assertion action - string callerIdentifier = context.CurrentNode.Description; + string callerIdentifier = context.CurrentNode.Subject.ToString(); assertionChain.OverrideCallerIdentifier(() => callerIdentifier); assertionChain.ReuseOnce(); diff --git a/Src/FluentAssertions/Equivalency/Steps/AutoConversionStep.cs b/Src/FluentAssertions/Equivalency/Steps/AutoConversionStep.cs index e91c7daf5b..9d45787682 100644 --- a/Src/FluentAssertions/Equivalency/Steps/AutoConversionStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/AutoConversionStep.cs @@ -37,14 +37,14 @@ public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationCon if (TryChangeType(comparands.Subject, expectationType, out object convertedSubject)) { context.Tracer.WriteLine(member => - Invariant($"Converted subject {comparands.Subject} at {member.Description} to {expectationType}")); + Invariant($"Converted subject {comparands.Subject} at {member.Subject} to {expectationType}")); comparands.Subject = convertedSubject; } else { context.Tracer.WriteLine(member => - Invariant($"Subject {comparands.Subject} at {member.Description} could not be converted to {expectationType}")); + Invariant($"Subject {comparands.Subject} at {member.Subject} could not be converted to {expectationType}")); } return EquivalencyResult.ContinueWithNext; diff --git a/Src/FluentAssertions/Equivalency/Steps/DictionaryEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/DictionaryEquivalencyStep.cs index 2b9f777887..b0582fe608 100644 --- a/Src/FluentAssertions/Equivalency/Steps/DictionaryEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/DictionaryEquivalencyStep.cs @@ -25,7 +25,7 @@ protected override EquivalencyResult OnHandle(Comparands comparands, if (context.Options.IsRecursive) { context.Tracer.WriteLine(member => - Invariant($"Recursing into dictionary item {key} at {member.Description}")); + Invariant($"Recursing into dictionary item {key} at {member.Subject}")); nestedValidator.AssertEquivalencyOf(new Comparands(subject[key], expectation[key], typeof(object)), context.AsDictionaryItem(key)); } @@ -33,7 +33,7 @@ protected override EquivalencyResult OnHandle(Comparands comparands, { context.Tracer.WriteLine(member => Invariant( - $"Comparing dictionary item {key} at {member.Description} between subject and expectation")); + $"Comparing dictionary item {key} at {member.Subject} between subject and expectation")); assertionChain.WithCallerPostfix($"[{key.ToFormattedString()}]").ReuseOnce(); subject[key].Should().Be(expectation[key], context.Reason.FormattedMessage, context.Reason.Arguments); diff --git a/Src/FluentAssertions/Equivalency/Steps/EnumerableEquivalencyValidator.cs b/Src/FluentAssertions/Equivalency/Steps/EnumerableEquivalencyValidator.cs index c2e7d5640f..af947cd91e 100644 --- a/Src/FluentAssertions/Equivalency/Steps/EnumerableEquivalencyValidator.cs +++ b/Src/FluentAssertions/Equivalency/Steps/EnumerableEquivalencyValidator.cs @@ -42,7 +42,7 @@ public void Execute(object[] subject, T[] expectation) if (Recursive) { using var _ = context.Tracer.WriteBlock(member => - Invariant($"Structurally comparing {subject} and expectation {expectation} at {member.Description}")); + Invariant($"Structurally comparing {subject} and expectation {expectation} at {member.Subject}")); AssertElementGraphEquivalency(subject, expectation, context.CurrentNode); } @@ -50,7 +50,7 @@ public void Execute(object[] subject, T[] expectation) { using var _ = context.Tracer.WriteBlock(member => Invariant( - $"Comparing subject {subject} and expectation {expectation} at {member.Description} using simple value equality")); + $"Comparing subject {subject} and expectation {expectation} at {member.Subject} using simple value equality")); subject.Should().BeEquivalentTo(expectation); } @@ -102,7 +102,7 @@ private void AssertElementGraphEquivalencyWithStrictOrdering(object[] subject using var _ = context.Tracer.WriteBlock(member => Invariant( - $"Strictly comparing expectation {expectation} at {member.Description} to item with index {index} in {subjects}")); + $"Strictly comparing expectation {expectation} at {member.Subject} to item with index {index} in {subjects}")); bool succeeded = StrictlyMatchAgainst(subjects, expectation, index); if (!succeeded) @@ -111,7 +111,7 @@ private void AssertElementGraphEquivalencyWithStrictOrdering(object[] subject if (failedCount >= FailedItemsFastFailThreshold) { context.Tracer.WriteLine(member => - $"Aborting strict order comparison of collections after {FailedItemsFastFailThreshold} items failed at {member.Description}"); + $"Aborting strict order comparison of collections after {FailedItemsFastFailThreshold} items failed at {member.Subject}"); break; } @@ -129,7 +129,7 @@ private void AssertElementGraphEquivalencyWithLooseOrdering(object[] subjects using var _ = context.Tracer.WriteBlock(member => Invariant( - $"Finding the best match of {expectation} within all items in {subjects} at {member.Description}[{index}]")); + $"Finding the best match of {expectation} within all items in {subjects} at {member.Subject}[{index}]")); bool succeeded = LooselyMatchAgainst(subjects, expectation, index); @@ -140,7 +140,7 @@ private void AssertElementGraphEquivalencyWithLooseOrdering(object[] subjects if (failedCount >= FailedItemsFastFailThreshold) { context.Tracer.WriteLine(member => - $"Fail failing loose order comparison of collection after {FailedItemsFastFailThreshold} items failed at {member.Description}"); + $"Fail failing loose order comparison of collection after {FailedItemsFastFailThreshold} items failed at {member.Subject}"); break; } @@ -156,7 +156,7 @@ private bool LooselyMatchAgainst(IList subjects, T expectation, int e int index = 0; GetTraceMessage getMessage = member => - $"Comparing subject at {member.Description}[{index}] with the expectation at {member.Description}[{expectationIndex}]"; + $"Comparing subject at {member.Subject}[{index}] with the expectation at {member.Subject}[{expectationIndex}]"; int indexToBeRemoved = -1; diff --git a/Src/FluentAssertions/Equivalency/Steps/StringEqualityEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/StringEqualityEquivalencyStep.cs index ec0e70fd24..13087b32d5 100644 --- a/Src/FluentAssertions/Equivalency/Steps/StringEqualityEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/StringEqualityEquivalencyStep.cs @@ -79,7 +79,7 @@ private static bool ValidateAgainstNulls(AssertionChain assertionChain, Comparan if (onlyOneNull) { assertionChain.FailWith( - $"Expected {currentNode.Description} to be {{0}}{{reason}}, but found {{1}}.", expected, subject); + $"Expected {currentNode.Subject.Description} to be {{0}}{{reason}}, but found {{1}}.", expected, subject); return false; } diff --git a/Src/FluentAssertions/Equivalency/Steps/StructuralEqualityEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/StructuralEqualityEquivalencyStep.cs index 0c43280b73..6ca11e99da 100644 --- a/Src/FluentAssertions/Equivalency/Steps/StructuralEqualityEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/StructuralEqualityEquivalencyStep.cs @@ -60,7 +60,6 @@ private static void AssertMemberEquality(Comparands comparands, IEquivalencyVali var assertionChain = AssertionChain.GetOrCreate().For(context); IMember matchingMember = FindMatchFor(selectedMember, context.CurrentNode, comparands.Subject, options, assertionChain); - if (matchingMember is not null) { var nestedComparands = new Comparands @@ -70,11 +69,11 @@ private static void AssertMemberEquality(Comparands comparands, IEquivalencyVali CompileTimeType = selectedMember.Type }; - if (selectedMember.Name != matchingMember.Name) + if (matchingMember.Subject.Name != selectedMember.Subject.Name) { // In case the matching process selected a different member on the subject, // adjust the current member so that assertion failures report the proper name. - selectedMember.Name = matchingMember.Name; + selectedMember.Subject = matchingMember.Subject; } parent.AssertEquivalencyOf(nestedComparands, context.AsNestedMember(selectedMember)); diff --git a/Src/FluentAssertions/Equivalency/Steps/ValueTypeEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/Steps/ValueTypeEquivalencyStep.cs index ee7d1b400d..92319e7e82 100644 --- a/Src/FluentAssertions/Equivalency/Steps/ValueTypeEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/Steps/ValueTypeEquivalencyStep.cs @@ -24,7 +24,7 @@ public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationCon ? $"{expectationType} overrides Equals" : "we are forced to use Equals"; - return $"Treating {member.Description} as a value type because {strategyName}."; + return $"Treating {member.Subject.Description} as a value type because {strategyName}."; }); AssertionChain.GetOrCreate() diff --git a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net47.verified.txt b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net47.verified.txt index e9a9d7633d..a8822757cd 100644 --- a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net47.verified.txt +++ b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net47.verified.txt @@ -792,14 +792,12 @@ namespace FluentAssertions.Equivalency public interface INode { int Depth { get; } - string Description { get; } + FluentAssertions.Equivalency.Pathway Expectation { get; } FluentAssertions.Equivalency.GetSubjectId GetSubjectId { get; } bool IsRoot { get; } - string Name { get; set; } System.Type ParentType { get; } - string Path { get; } - string PathAndName { get; } bool RootIsCollection { get; } + FluentAssertions.Equivalency.Pathway Subject { get; set; } System.Type Type { get; } } public interface IObjectInfo @@ -858,6 +856,18 @@ namespace FluentAssertions.Equivalency public System.Collections.Generic.IEnumerator GetEnumerator() { } public bool IsOrderingStrictFor(FluentAssertions.Equivalency.IObjectInfo objectInfo) { } } + public class Pathway : System.IEquatable + { + public Pathway(FluentAssertions.Equivalency.Pathway original) { } + public Pathway(FluentAssertions.Equivalency.Pathway parent, string name, FluentAssertions.Equivalency.Pathway.GetDescription getDescription) { } + public Pathway(string path, string name, FluentAssertions.Equivalency.Pathway.GetDescription getDescription) { } + public string Description { get; } + public string PathAndName { get; } + public string Name { get; init; } + public string Path { get; init; } + public override string ToString() { } + public delegate string GetDescription(string pathAndName); + } public abstract class SelfReferenceEquivalencyOptions : FluentAssertions.Equivalency.IEquivalencyOptions where TSelf : FluentAssertions.Equivalency.SelfReferenceEquivalencyOptions { diff --git a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net6.0.verified.txt b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net6.0.verified.txt index 879f1f7aca..f3cece19dd 100644 --- a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net6.0.verified.txt +++ b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net6.0.verified.txt @@ -805,14 +805,12 @@ namespace FluentAssertions.Equivalency public interface INode { int Depth { get; } - string Description { get; } + FluentAssertions.Equivalency.Pathway Expectation { get; } FluentAssertions.Equivalency.GetSubjectId GetSubjectId { get; } bool IsRoot { get; } - string Name { get; set; } System.Type ParentType { get; } - string Path { get; } - string PathAndName { get; } bool RootIsCollection { get; } + FluentAssertions.Equivalency.Pathway Subject { get; set; } System.Type Type { get; } } public interface IObjectInfo @@ -871,6 +869,18 @@ namespace FluentAssertions.Equivalency public System.Collections.Generic.IEnumerator GetEnumerator() { } public bool IsOrderingStrictFor(FluentAssertions.Equivalency.IObjectInfo objectInfo) { } } + public class Pathway : System.IEquatable + { + public Pathway(FluentAssertions.Equivalency.Pathway original) { } + public Pathway(FluentAssertions.Equivalency.Pathway parent, string name, FluentAssertions.Equivalency.Pathway.GetDescription getDescription) { } + public Pathway(string path, string name, FluentAssertions.Equivalency.Pathway.GetDescription getDescription) { } + public string Description { get; } + public string PathAndName { get; } + public string Name { get; init; } + public string Path { get; init; } + public override string ToString() { } + public delegate string GetDescription(string pathAndName); + } public abstract class SelfReferenceEquivalencyOptions : FluentAssertions.Equivalency.IEquivalencyOptions where TSelf : FluentAssertions.Equivalency.SelfReferenceEquivalencyOptions { diff --git a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.0.verified.txt b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.0.verified.txt index 2261b3dd0e..1045dfe9b6 100644 --- a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.0.verified.txt +++ b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.0.verified.txt @@ -784,14 +784,12 @@ namespace FluentAssertions.Equivalency public interface INode { int Depth { get; } - string Description { get; } + FluentAssertions.Equivalency.Pathway Expectation { get; } FluentAssertions.Equivalency.GetSubjectId GetSubjectId { get; } bool IsRoot { get; } - string Name { get; set; } System.Type ParentType { get; } - string Path { get; } - string PathAndName { get; } bool RootIsCollection { get; } + FluentAssertions.Equivalency.Pathway Subject { get; set; } System.Type Type { get; } } public interface IObjectInfo @@ -850,6 +848,18 @@ namespace FluentAssertions.Equivalency public System.Collections.Generic.IEnumerator GetEnumerator() { } public bool IsOrderingStrictFor(FluentAssertions.Equivalency.IObjectInfo objectInfo) { } } + public class Pathway : System.IEquatable + { + public Pathway(FluentAssertions.Equivalency.Pathway original) { } + public Pathway(FluentAssertions.Equivalency.Pathway parent, string name, FluentAssertions.Equivalency.Pathway.GetDescription getDescription) { } + public Pathway(string path, string name, FluentAssertions.Equivalency.Pathway.GetDescription getDescription) { } + public string Description { get; } + public string PathAndName { get; } + public string Name { get; init; } + public string Path { get; init; } + public override string ToString() { } + public delegate string GetDescription(string pathAndName); + } public abstract class SelfReferenceEquivalencyOptions : FluentAssertions.Equivalency.IEquivalencyOptions where TSelf : FluentAssertions.Equivalency.SelfReferenceEquivalencyOptions { diff --git a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.1.verified.txt b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.1.verified.txt index 0702e96d54..88e08ae801 100644 --- a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.1.verified.txt +++ b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.1.verified.txt @@ -792,14 +792,12 @@ namespace FluentAssertions.Equivalency public interface INode { int Depth { get; } - string Description { get; } + FluentAssertions.Equivalency.Pathway Expectation { get; } FluentAssertions.Equivalency.GetSubjectId GetSubjectId { get; } bool IsRoot { get; } - string Name { get; set; } System.Type ParentType { get; } - string Path { get; } - string PathAndName { get; } bool RootIsCollection { get; } + FluentAssertions.Equivalency.Pathway Subject { get; set; } System.Type Type { get; } } public interface IObjectInfo @@ -858,6 +856,18 @@ namespace FluentAssertions.Equivalency public System.Collections.Generic.IEnumerator GetEnumerator() { } public bool IsOrderingStrictFor(FluentAssertions.Equivalency.IObjectInfo objectInfo) { } } + public class Pathway : System.IEquatable + { + public Pathway(FluentAssertions.Equivalency.Pathway original) { } + public Pathway(FluentAssertions.Equivalency.Pathway parent, string name, FluentAssertions.Equivalency.Pathway.GetDescription getDescription) { } + public Pathway(string path, string name, FluentAssertions.Equivalency.Pathway.GetDescription getDescription) { } + public string Description { get; } + public string PathAndName { get; } + public string Name { get; init; } + public string Path { get; init; } + public override string ToString() { } + public delegate string GetDescription(string pathAndName); + } public abstract class SelfReferenceEquivalencyOptions : FluentAssertions.Equivalency.IEquivalencyOptions where TSelf : FluentAssertions.Equivalency.SelfReferenceEquivalencyOptions { diff --git a/Tests/FluentAssertions.Equivalency.Specs/BasicSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/BasicSpecs.cs index b1c54dd353..de6f2016d0 100644 --- a/Tests/FluentAssertions.Equivalency.Specs/BasicSpecs.cs +++ b/Tests/FluentAssertions.Equivalency.Specs/BasicSpecs.cs @@ -580,7 +580,7 @@ public void When_asserting_equivalence_including_only_fields_it_should_not_match // Assert act.Should().Throw() - .WithMessage("Expectation has field onlyAProperty.Value that the other object does not have.*"); + .WithMessage("Expectation has field Value that the other object does not have.*"); } [Fact] @@ -595,7 +595,7 @@ public void When_asserting_equivalence_including_only_properties_it_should_not_m // Assert act.Should().Throw() - .WithMessage("Expectation has property onlyAField.Value that the other object does not have*"); + .WithMessage("Expectation has property Value that the other object does not have*"); } [Fact] diff --git a/Tests/FluentAssertions.Equivalency.Specs/ExtensibilitySpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/ExtensibilitySpecs.cs index af8beba7ce..f586ebb26f 100644 --- a/Tests/FluentAssertions.Equivalency.Specs/ExtensibilitySpecs.cs +++ b/Tests/FluentAssertions.Equivalency.Specs/ExtensibilitySpecs.cs @@ -76,7 +76,7 @@ public bool OverridesStandardIncludeRules public IEnumerable SelectMembers(INode currentNode, IEnumerable selectedMembers, MemberSelectionContext context) { - return selectedMembers.Where(pi => !pi.Name.EndsWith("Id", StringComparison.Ordinal)).ToArray(); + return selectedMembers.Where(pi => !pi.Subject.Name.EndsWith("Id", StringComparison.Ordinal)).ToArray(); } bool IMemberSelectionRule.IncludesMembers @@ -145,7 +145,7 @@ internal class ForeignKeyMatchingRule : IMemberMatchingRule public IMember Match(IMember expectedMember, object subject, INode parent, IEquivalencyOptions options, AssertionChain assertionChain) { - string name = expectedMember.Name; + string name = expectedMember.Subject.Name; if (name.EndsWith("Id", StringComparison.Ordinal)) { diff --git a/Tests/FluentAssertions.Equivalency.Specs/MemberLessObjectsSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/MemberLessObjectsSpecs.cs index 688bfa5884..d1768baad8 100644 --- a/Tests/FluentAssertions.Equivalency.Specs/MemberLessObjectsSpecs.cs +++ b/Tests/FluentAssertions.Equivalency.Specs/MemberLessObjectsSpecs.cs @@ -139,7 +139,7 @@ public void When_throwing_on_missing_members_and_there_is_a_missing_member_shoul // Assert act.Should().Throw() - .WithMessage("Expectation has property subject.Age that the other object does not have*"); + .WithMessage("Expectation has property Age that the other object does not have*"); } [Fact] diff --git a/Tests/FluentAssertions.Equivalency.Specs/MemberMatchingSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/MemberMatchingSpecs.cs index 1fdd15a919..a4b032705c 100644 --- a/Tests/FluentAssertions.Equivalency.Specs/MemberMatchingSpecs.cs +++ b/Tests/FluentAssertions.Equivalency.Specs/MemberMatchingSpecs.cs @@ -1,5 +1,6 @@ -using System; +using System; using System.Diagnostics.CodeAnalysis; +using JetBrains.Annotations; using Xunit; using Xunit.Sdk; @@ -87,7 +88,8 @@ public void Nested_properties_can_be_mapped_using_a_nested_type_and_property_nam public void Nested_explicitly_implemented_properties_can_be_mapped_using_a_nested_type_and_property_names() { // Arrange - var subject = new ParentOfSubjectWithExplicitlyImplementedProperty([new SubjectWithExplicitImplementedProperty()]); + var subject = + new ParentOfSubjectWithExplicitlyImplementedProperty(new[] { new SubjectWithExplicitImplementedProperty() }); ((IProperty)subject.Children[0]).Property = "Hello"; @@ -537,10 +539,166 @@ public void Can_map_members_of_a_root_collection() c.WithMapping(s => s.EntityId, d => d.Id)); } + [Fact] + public void Can_explicitly_include_a_property_on_a_mapped_type() + { + // Arrange + var expectation = new CustomerWithPropertiesDto + { + AddressInformation = new CustomerWithPropertiesDto.ResidenceDto + { + Address = "123 Main St", + IsValidated = true, + }, + }; + + var subject = new CustomerWithProperty + { + Address = new CustomerWithProperty.Residence + { + Address = "123 Main St", + }, + }; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation, o => o + .Including(r => r.AddressInformation.Address) + .WithMapping(s => s.AddressInformation, d => d.Address)); + } + + [Fact] + public void Can_exclude_a_property_on_a_mapped_type() + { + // Arrange + var expectation = new CustomerWithPropertiesDto + { + AddressInformation = new CustomerWithPropertiesDto.ResidenceDto + { + Address = "123 Main St", + IsValidated = true, + }, + }; + + var subject = new CustomerWithProperty + { + Address = new CustomerWithProperty.Residence + { + Address = "123 Main St", + }, + }; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation, o => o + .Excluding(r => r.AddressInformation.IsValidated) + .WithMapping(s => s.AddressInformation, d => d.Address)); + } + + [Fact] + public void Can_explicitly_include_a_field_on_a_mapped_type() + { + // Arrange + var expectation = new CustomerWithFieldDto + { + AddressInformation = new CustomerWithFieldDto.ResidenceDto + { + Address = "123 Main St", + IsValidated = true, + }, + }; + + var subject = new CustomerWithField + { + Address = new CustomerWithField.Residence + { + Address = "123 Main St", + }, + }; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation, o => o + .Including(r => r.AddressInformation.Address) + .WithMapping(s => s.AddressInformation, d => d.Address)); + } + + [Fact] + public void Can_exclude_a_field_on_a_mapped_type() + { + // Arrange + var expectation = new CustomerWithFieldDto + { + AddressInformation = new CustomerWithFieldDto.ResidenceDto + { + Address = "123 Main St", + IsValidated = true, + }, + }; + + var subject = new CustomerWithField + { + Address = new CustomerWithField.Residence + { + Address = "123 Main St", + }, + }; + + // Act / Assert + subject.Should().BeEquivalentTo(expectation, o => o + .Excluding(r => r.AddressInformation.IsValidated) + .WithMapping(s => s.AddressInformation, d => d.Address)); + } + + private class CustomerWithProperty + { + public Residence Address { get; set; } + + public class Residence + { + [UsedImplicitly] + public string Address { get; set; } + } + } + + private class CustomerWithPropertiesDto + { + public ResidenceDto AddressInformation { get; set; } + + public class ResidenceDto + { + public string Address { get; set; } + + public bool IsValidated { get; set; } + } + } + + private class CustomerWithField + { + public Residence Address; + + public class Residence + { + [UsedImplicitly] + public string Address; + } + } + + private class CustomerWithFieldDto + { + public ResidenceDto AddressInformation; + + public class ResidenceDto + { + public string Address; + + [UsedImplicitly] + public bool IsValidated; + } + } + private class Entity { public int EntityId { get; init; } + [UsedImplicitly] public string Name { get; init; } } @@ -548,6 +706,7 @@ private class EntityDto { public int Id { get; init; } + [UsedImplicitly] public string Name { get; init; } } diff --git a/Tests/FluentAssertions.Equivalency.Specs/NestedPropertiesSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/NestedPropertiesSpecs.cs index d9246bb008..c8fb18c2a0 100644 --- a/Tests/FluentAssertions.Equivalency.Specs/NestedPropertiesSpecs.cs +++ b/Tests/FluentAssertions.Equivalency.Specs/NestedPropertiesSpecs.cs @@ -229,7 +229,7 @@ public void When_not_all_the_properties_of_the_nested_object_exist_on_the_expect // Assert act .Should().Throw() - .WithMessage("Expectation has property subject.Level.OtherProperty that the other object does not have*"); + .WithMessage("Expectation has property Level.OtherProperty that the other object does not have*"); } [Fact] diff --git a/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.Basic.cs b/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.Basic.cs index bcea272ff6..53e10cf7f0 100644 --- a/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.Basic.cs +++ b/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.Basic.cs @@ -31,7 +31,7 @@ public void Property_names_are_case_sensitive() // Assert act.Should().Throw().WithMessage( - "Expectation*subject.name**other*not have*"); + "Expectation*name**other*not have*"); } [Fact] @@ -53,7 +53,7 @@ public void Field_names_are_case_sensitive() // Assert act.Should().Throw().WithMessage( - "Expectation*subject.name**other*not have*"); + "Expectation*name**other*not have*"); } private class ClassWithFieldInLowerCase @@ -120,7 +120,7 @@ public void When_the_expected_object_has_a_property_not_available_on_the_subject // Assert act.Should().Throw().WithMessage( - "Expectation has property subject.City that the other object does not have*"); + "Expectation has property City that the other object does not have*"); } [Fact] diff --git a/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.Browsability.cs b/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.Browsability.cs index 2754452d5e..dd93169c9c 100644 --- a/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.Browsability.cs +++ b/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.Browsability.cs @@ -248,7 +248,7 @@ public void When_non_browsable_property_on_subject_is_ignored_but_is_present_on_ // Assert action.Should().Throw().WithMessage( - "Expectation has * subject.*ThatMightBeNonBrowsable that is non-browsable in the other object, and non-browsable " + + "Expectation has*ThatMightBeNonBrowsable that is non-browsable in the other object, and non-browsable " + "members on the subject are ignored with the current configuration*"); } diff --git a/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.cs index d7e8a097ff..f5b18afeda 100644 --- a/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.cs +++ b/Tests/FluentAssertions.Equivalency.Specs/SelectionRulesSpecs.cs @@ -1,4 +1,4 @@ -using System; +using System; using FluentAssertions.Equivalency.Matching; using FluentAssertions.Equivalency.Ordering; using FluentAssertions.Equivalency.Selection; diff --git a/Tests/FluentAssertions.Specs/Execution/CallerIdentificationSpecs.cs b/Tests/FluentAssertions.Specs/Execution/CallerIdentificationSpecs.cs index 564ad48dea..a298db914a 100644 --- a/Tests/FluentAssertions.Specs/Execution/CallerIdentificationSpecs.cs +++ b/Tests/FluentAssertions.Specs/Execution/CallerIdentificationSpecs.cs @@ -567,7 +567,7 @@ 5. Test var node = Node.From(GetSubjectId); // Assert - node.Description.Should().StartWith("node.Description"); + node.Subject.Description.Should().StartWith("node.Subject.Description"); } [CustomAssertion] diff --git a/Tests/FluentAssertions.Specs/Numeric/ComparableSpecs.cs b/Tests/FluentAssertions.Specs/Numeric/ComparableSpecs.cs index c762fe199d..7f02063c19 100644 --- a/Tests/FluentAssertions.Specs/Numeric/ComparableSpecs.cs +++ b/Tests/FluentAssertions.Specs/Numeric/ComparableSpecs.cs @@ -236,7 +236,7 @@ public void When_two_instances_are_not_equivalent_it_should_throw() act .Should().Throw() .WithMessage( - "Expectation has property subject.SomeOtherProperty*that the other object does not have*"); + "Expectation has property SomeOtherProperty*that the other object does not have*"); } }