diff --git a/Src/FluentAssertions/Equivalency/Execution/ObjectInfo.cs b/Src/FluentAssertions/Equivalency/Execution/ObjectInfo.cs index 62c233fd28..0f45024f21 100644 --- a/Src/FluentAssertions/Equivalency/Execution/ObjectInfo.cs +++ b/Src/FluentAssertions/Equivalency/Execution/ObjectInfo.cs @@ -7,6 +7,7 @@ internal class ObjectInfo : IObjectInfo public ObjectInfo(Comparands comparands, INode currentNode) { Type = currentNode.Type; + ParentType = currentNode.ParentType; Path = currentNode.PathAndName; CompileTimeType = comparands.CompileTimeType; RuntimeType = comparands.RuntimeType; @@ -14,6 +15,8 @@ public ObjectInfo(Comparands comparands, INode currentNode) public Type Type { get; } + public Type ParentType { get; } + public string Path { get; set; } public Type CompileTimeType { get; } diff --git a/Src/FluentAssertions/Equivalency/Field.cs b/Src/FluentAssertions/Equivalency/Field.cs index 4e6124aee8..d82f1e46bc 100644 --- a/Src/FluentAssertions/Equivalency/Field.cs +++ b/Src/FluentAssertions/Equivalency/Field.cs @@ -27,6 +27,7 @@ public Field(Type reflectedType, FieldInfo fieldInfo, INode parent) GetSubjectId = parent.GetSubjectId; Name = fieldInfo.Name; Type = fieldInfo.FieldType; + ParentType = fieldInfo.DeclaringType; RootIsCollection = parent.RootIsCollection; } diff --git a/Src/FluentAssertions/Equivalency/INode.cs b/Src/FluentAssertions/Equivalency/INode.cs index f95657c1b5..4e2abbd478 100644 --- a/Src/FluentAssertions/Equivalency/INode.cs +++ b/Src/FluentAssertions/Equivalency/INode.cs @@ -1,9 +1,11 @@ using System; +using JetBrains.Annotations; namespace FluentAssertions.Equivalency; /// -/// Represents a node in the object graph as it is expected in a structural equivalency check. +/// Represents a node in the object graph that is being compared as part of a structural equivalency check. +/// This can be the root object, a collection item, a dictionary element, a property or a field. /// public interface INode { @@ -22,10 +24,19 @@ public interface INode string Name { get; set; } /// - /// Gets the type of this node. + /// Gets the type of this node, e.g. the type of the field or property, or the type of the collection item. /// Type Type { get; } + /// + /// Gets the type of the parent node, e.g. the type that declares a property or field. + /// + /// + /// Is null 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. /// diff --git a/Src/FluentAssertions/Equivalency/IObjectInfo.cs b/Src/FluentAssertions/Equivalency/IObjectInfo.cs index 615cd119b9..1b494a0416 100644 --- a/Src/FluentAssertions/Equivalency/IObjectInfo.cs +++ b/Src/FluentAssertions/Equivalency/IObjectInfo.cs @@ -1,4 +1,5 @@ using System; +using JetBrains.Annotations; namespace FluentAssertions.Equivalency; @@ -8,10 +9,20 @@ namespace FluentAssertions.Equivalency; public interface IObjectInfo { /// - /// Gets the type of this node. + /// Gets the type of the object /// + [Obsolete("Use CompileTimeType or RuntimeType instead")] Type Type { get; } + /// + /// Gets the type of the parent, e.g. the type that declares a property or field. + /// + /// + /// Is null for the root object. + /// + [CanBeNull] + Type ParentType { get; } + /// /// Gets the full path from the root object until the current node separated by dots. /// diff --git a/Src/FluentAssertions/Equivalency/Node.cs b/Src/FluentAssertions/Equivalency/Node.cs index 71315cfd32..90d94a3042 100644 --- a/Src/FluentAssertions/Equivalency/Node.cs +++ b/Src/FluentAssertions/Equivalency/Node.cs @@ -1,15 +1,11 @@ using System; using System.Collections; -using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using FluentAssertions.Common; namespace FluentAssertions.Equivalency; -/// -/// Represents a node in the object graph that is being compared as part of a structural equivalency check. -/// public class Node : INode { private static readonly Regex MatchFirstIndex = new(@"^\[\d+\]$"); @@ -22,6 +18,8 @@ public class Node : INode public Type Type { get; protected set; } + public Type ParentType { get; protected set; } + public string Path { get => path; @@ -82,6 +80,7 @@ public static INode From(GetSubjectId getSubjectId) Name = string.Empty, Path = string.Empty, Type = typeof(T), + ParentType = null, RootIsCollection = IsCollection(typeof(T)) }; } @@ -91,6 +90,7 @@ public static INode FromCollectionItem(string index, INode parent) return new Node { Type = typeof(T), + ParentType = parent.Type, Name = "[" + index + "]", Path = parent.PathAndName, GetSubjectId = parent.GetSubjectId, @@ -103,6 +103,7 @@ public static INode FromDictionaryItem(object key, INode parent) return new Node { Type = typeof(T), + ParentType = parent.Type, Name = "[" + key + "]", Path = parent.PathAndName, GetSubjectId = parent.GetSubjectId, diff --git a/Src/FluentAssertions/Equivalency/Ordering/CollectionMemberObjectInfo.cs b/Src/FluentAssertions/Equivalency/Ordering/CollectionMemberObjectInfo.cs index ac2898ddd3..5f5b9d63c3 100644 --- a/Src/FluentAssertions/Equivalency/Ordering/CollectionMemberObjectInfo.cs +++ b/Src/FluentAssertions/Equivalency/Ordering/CollectionMemberObjectInfo.cs @@ -7,7 +7,12 @@ internal class CollectionMemberObjectInfo : IObjectInfo public CollectionMemberObjectInfo(IObjectInfo context) { Path = GetAdjustedPropertyPath(context.Path); + +#pragma warning disable CS0618 Type = context.Type; +#pragma warning restore CS0618 + + ParentType = context.ParentType; RuntimeType = context.RuntimeType; CompileTimeType = context.CompileTimeType; } @@ -18,6 +23,8 @@ private static string GetAdjustedPropertyPath(string propertyPath) } public Type Type { get; } + + public Type ParentType { get; } public string Path { get; set; } diff --git a/Src/FluentAssertions/Equivalency/Property.cs b/Src/FluentAssertions/Equivalency/Property.cs index 93e2429ccc..4ef87c21b9 100644 --- a/Src/FluentAssertions/Equivalency/Property.cs +++ b/Src/FluentAssertions/Equivalency/Property.cs @@ -26,6 +26,7 @@ public Property(Type reflectedType, PropertyInfo propertyInfo, INode parent) DeclaringType = propertyInfo.DeclaringType; Name = propertyInfo.Name; Type = propertyInfo.PropertyType; + ParentType = propertyInfo.DeclaringType; Path = parent.PathAndName; GetSubjectId = parent.GetSubjectId; RootIsCollection = parent.RootIsCollection; @@ -36,7 +37,7 @@ public object GetValue(object obj) return propertyInfo.GetValue(obj); } - public Type DeclaringType { get; private set; } + public Type DeclaringType { get; } public Type ReflectedType { get; } diff --git a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net47.verified.txt b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net47.verified.txt index 68db356d87..f2d2ba526e 100644 --- a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net47.verified.txt +++ b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net47.verified.txt @@ -935,6 +935,7 @@ namespace FluentAssertions.Equivalency 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; } @@ -943,8 +944,10 @@ namespace FluentAssertions.Equivalency public interface IObjectInfo { System.Type CompileTimeType { get; } + System.Type ParentType { get; } string Path { get; set; } System.Type RuntimeType { get; } + [System.Obsolete("Use CompileTimeType or RuntimeType instead")] System.Type Type { get; } } public interface IOrderingRule @@ -982,6 +985,7 @@ namespace FluentAssertions.Equivalency public FluentAssertions.Equivalency.GetSubjectId GetSubjectId { get; set; } public bool IsRoot { get; } public string Name { get; set; } + public System.Type ParentType { get; set; } public string Path { get; set; } public string PathAndName { get; } public bool RootIsCollection { get; set; } diff --git a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net6.0.verified.txt b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net6.0.verified.txt index be8713a032..6f8abf4512 100644 --- a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net6.0.verified.txt +++ b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net6.0.verified.txt @@ -947,6 +947,7 @@ namespace FluentAssertions.Equivalency 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; } @@ -955,8 +956,10 @@ namespace FluentAssertions.Equivalency public interface IObjectInfo { System.Type CompileTimeType { get; } + System.Type ParentType { get; } string Path { get; set; } System.Type RuntimeType { get; } + [System.Obsolete("Use CompileTimeType or RuntimeType instead")] System.Type Type { get; } } public interface IOrderingRule @@ -994,6 +997,7 @@ namespace FluentAssertions.Equivalency public FluentAssertions.Equivalency.GetSubjectId GetSubjectId { get; set; } public bool IsRoot { get; } public string Name { get; set; } + public System.Type ParentType { get; set; } public string Path { get; set; } public string PathAndName { get; } public bool RootIsCollection { get; set; } diff --git a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netcoreapp2.1.verified.txt b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netcoreapp2.1.verified.txt index da807b1d68..9e06dba6fd 100644 --- a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netcoreapp2.1.verified.txt +++ b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netcoreapp2.1.verified.txt @@ -935,6 +935,7 @@ namespace FluentAssertions.Equivalency 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; } @@ -943,8 +944,10 @@ namespace FluentAssertions.Equivalency public interface IObjectInfo { System.Type CompileTimeType { get; } + System.Type ParentType { get; } string Path { get; set; } System.Type RuntimeType { get; } + [System.Obsolete("Use CompileTimeType or RuntimeType instead")] System.Type Type { get; } } public interface IOrderingRule @@ -982,6 +985,7 @@ namespace FluentAssertions.Equivalency public FluentAssertions.Equivalency.GetSubjectId GetSubjectId { get; set; } public bool IsRoot { get; } public string Name { get; set; } + public System.Type ParentType { get; set; } public string Path { get; set; } public string PathAndName { get; } public bool RootIsCollection { get; set; } diff --git a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netcoreapp3.0.verified.txt b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netcoreapp3.0.verified.txt index 0e54c1699a..26f5978a06 100644 --- a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netcoreapp3.0.verified.txt +++ b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netcoreapp3.0.verified.txt @@ -935,6 +935,7 @@ namespace FluentAssertions.Equivalency 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; } @@ -943,8 +944,10 @@ namespace FluentAssertions.Equivalency public interface IObjectInfo { System.Type CompileTimeType { get; } + System.Type ParentType { get; } string Path { get; set; } System.Type RuntimeType { get; } + [System.Obsolete("Use CompileTimeType or RuntimeType instead")] System.Type Type { get; } } public interface IOrderingRule @@ -982,6 +985,7 @@ namespace FluentAssertions.Equivalency public FluentAssertions.Equivalency.GetSubjectId GetSubjectId { get; set; } public bool IsRoot { get; } public string Name { get; set; } + public System.Type ParentType { get; set; } public string Path { get; set; } public string PathAndName { get; } public bool RootIsCollection { get; set; } diff --git a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.0.verified.txt b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.0.verified.txt index eeaa8fe8dc..d64dbc6ef5 100644 --- a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.0.verified.txt +++ b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.0.verified.txt @@ -928,6 +928,7 @@ namespace FluentAssertions.Equivalency 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; } @@ -936,8 +937,10 @@ namespace FluentAssertions.Equivalency public interface IObjectInfo { System.Type CompileTimeType { get; } + System.Type ParentType { get; } string Path { get; set; } System.Type RuntimeType { get; } + [System.Obsolete("Use CompileTimeType or RuntimeType instead")] System.Type Type { get; } } public interface IOrderingRule @@ -975,6 +978,7 @@ namespace FluentAssertions.Equivalency public FluentAssertions.Equivalency.GetSubjectId GetSubjectId { get; set; } public bool IsRoot { get; } public string Name { get; set; } + public System.Type ParentType { get; set; } public string Path { get; set; } public string PathAndName { get; } public bool RootIsCollection { get; set; } diff --git a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.1.verified.txt b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.1.verified.txt index 1e4f06c121..fdddf3faf8 100644 --- a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.1.verified.txt +++ b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.1.verified.txt @@ -935,6 +935,7 @@ namespace FluentAssertions.Equivalency 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; } @@ -943,8 +944,10 @@ namespace FluentAssertions.Equivalency public interface IObjectInfo { System.Type CompileTimeType { get; } + System.Type ParentType { get; } string Path { get; set; } System.Type RuntimeType { get; } + [System.Obsolete("Use CompileTimeType or RuntimeType instead")] System.Type Type { get; } } public interface IOrderingRule @@ -982,6 +985,7 @@ namespace FluentAssertions.Equivalency public FluentAssertions.Equivalency.GetSubjectId GetSubjectId { get; set; } public bool IsRoot { get; } public string Name { get; set; } + public System.Type ParentType { get; set; } public string Path { get; set; } public string PathAndName { get; } public bool RootIsCollection { get; set; } diff --git a/Tests/FluentAssertions.Equivalency.Specs/ExtensibilitySpecs.cs b/Tests/FluentAssertions.Equivalency.Specs/ExtensibilitySpecs.cs index ff66adec94..f6dfce3a8b 100644 --- a/Tests/FluentAssertions.Equivalency.Specs/ExtensibilitySpecs.cs +++ b/Tests/FluentAssertions.Equivalency.Specs/ExtensibilitySpecs.cs @@ -226,6 +226,27 @@ public void When_property_of_other_is_incompatible_with_generic_type_the_message .WithMessage("*Id*from expectation*System.String*System.Double*"); } + [Fact] + public void Can_exclude_all_properties_of_the_parent_type() + { + // Arrange + var subject = new + { + Id = "foo", + }; + + var expectation = new + { + Id = "bar", + }; + + // Act + subject.Should().BeEquivalentTo(expectation, + o => o + .Using(c => c.Subject.Should().HaveLength(c.Expectation.Length)) + .When(si => si.ParentType == expectation.GetType() && si.Path.EndsWith("Id", StringComparison.InvariantCulture))); + } + [Fact] public void When_property_of_subject_is_incompatible_with_generic_type_the_message_should_include_generic_type() { diff --git a/docs/_pages/releases.md b/docs/_pages/releases.md index c81b898eaa..ffe878de27 100644 --- a/docs/_pages/releases.md +++ b/docs/_pages/releases.md @@ -12,6 +12,7 @@ sidebar: ### What's new * Added `ContainInConsecutiveOrder` and `NotContainInConsecutiveOrder` assertions to check if a collection contains items in a specific order and to be consecutive - [#1963](https://github.com/fluentassertions/fluentassertions/pull/1963) * Added `NotCompleteWithinAsync` for assertions on `Task` - [#1967](https://github.com/fluentassertions/fluentassertions/pull/1967) +* Added a `ParentType` to `IObjectInfo` to help determining the parent in a call to `Using`/`When` constructs - [#1950](https://github.com/fluentassertions/fluentassertions/pull/1950) ### Fixes * Fix `For`/`Exclude` not excluding properties in objects in a collection - [#1953](https://github.com/fluentassertions/fluentassertions/pull/1953)