From c03fc287e4c26e78730f8f9f5d623bb2d5cd0314 Mon Sep 17 00:00:00 2001 From: Dennis Doomen Date: Sat, 2 Jul 2022 18:32:48 +0200 Subject: [PATCH] Adds IObjectInfo.ParentType to help determining the declaring type in a call to Using/When constructs - --- .../Equivalency/Execution/ObjectInfo.cs | 3 +++ Src/FluentAssertions/Equivalency/Field.cs | 1 + Src/FluentAssertions/Equivalency/INode.cs | 15 +++++++++++-- .../Equivalency/IObjectInfo.cs | 13 +++++++++++- Src/FluentAssertions/Equivalency/Node.cs | 9 ++++---- .../Ordering/CollectionMemberObjectInfo.cs | 7 +++++++ Src/FluentAssertions/Equivalency/Property.cs | 3 ++- .../ExtensibilitySpecs.cs | 21 +++++++++++++++++++ docs/_pages/releases.md | 1 + 9 files changed, 65 insertions(+), 8 deletions(-) diff --git a/Src/FluentAssertions/Equivalency/Execution/ObjectInfo.cs b/Src/FluentAssertions/Equivalency/Execution/ObjectInfo.cs index 62c233fd28..a0412f91fd 100644 --- a/Src/FluentAssertions/Equivalency/Execution/ObjectInfo.cs +++ b/Src/FluentAssertions/Equivalency/Execution/ObjectInfo.cs @@ -7,12 +7,15 @@ 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; } public Type Type { get; } + + public Type ParentType { get; } public string Path { get; set; } 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..ee0d55de37 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,9 +24,18 @@ 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..62513f6aa1 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,9 +9,19 @@ 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..3b2d115d21 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+\]$"); @@ -21,6 +17,8 @@ public class Node : INode public GetSubjectId GetSubjectId { get; protected set; } = () => string.Empty; public Type Type { get; protected set; } + + public Type ParentType { get; protected set; } public string 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/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 a8097f9da2..e25e1450d0 100644 --- a/docs/_pages/releases.md +++ b/docs/_pages/releases.md @@ -10,6 +10,7 @@ sidebar: ## Unreleased ### What's new +* Adds a `ParentType` to `IObjectInfo` to help determining the parent in a call to `Using`/`When` constructs - [#2000](https://github.com/fluentassertions/fluentassertions/pull/2000) ### Fixes * Fix `For`/`Exclude` not excluding properties in objects in a collection - [#1953](https://github.com/fluentassertions/fluentassertions/pull/1953)