public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, IEquivalencyValidator nestedValidator)
        {
            Type             expectationType = comparands.GetExpectedType(context.Options);
            EqualityStrategy strategy        = context.Options.GetEqualityStrategy(expectationType);

            bool canHandle = (strategy == EqualityStrategy.Equals) || (strategy == EqualityStrategy.ForceEquals);

            if (canHandle)
            {
                context.Tracer.WriteLine(member =>
                {
                    string strategyName = (strategy == EqualityStrategy.Equals)
                        ? "Equals must be used" : "object overrides Equals";

                    return($"Treating {member.Description} as a value type because {strategyName}.");
                });

                comparands.Subject.Should().Be(comparands.Expectation, context.Reason.FormattedMessage, context.Reason.Arguments);

                return(EquivalencyResult.AssertionCompleted);
            }
            else
            {
                return(EquivalencyResult.ContinueWithNext);
            }
        }
Example #2
0
        public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, IEquivalencyValidator nestedValidator)
        {
            if (!context.Options.ConversionSelector.RequiresConversion(comparands, context.CurrentNode))
            {
                return(EquivalencyResult.ContinueWithNext);
            }

            if ((comparands.Expectation is null) || (comparands.Subject is null))
            {
                return(EquivalencyResult.ContinueWithNext);
            }

            Type subjectType     = comparands.Subject.GetType();
            Type expectationType = comparands.Expectation.GetType();

            if (subjectType.IsSameOrInherits(expectationType))
            {
                return(EquivalencyResult.ContinueWithNext);
            }

            if (TryChangeType(comparands.Subject, expectationType, out object convertedSubject))
            {
                context.Tracer.WriteLine(member => Invariant($"Converted subject {comparands.Subject} at {member.Description} to {expectationType}"));

                comparands.Subject = convertedSubject;
            }
            else
            {
                context.Tracer.WriteLine(member => Invariant($"Subject {comparands.Subject} at {member.Description} could not be converted to {expectationType}"));
            }

            return(EquivalencyResult.ContinueWithNext);
        }
Example #3
0
 private static void AssertDictionaryEquivalence(Comparands comparands, IEquivalencyValidationContext context,
                                                 IEquivalencyValidator parent, DictionaryInterfaceInfo actualDictionary, DictionaryInterfaceInfo expectedDictionary)
 {
     AssertDictionaryEquivalenceMethod
     .MakeGenericMethod(actualDictionary.Key, actualDictionary.Value, expectedDictionary.Key, expectedDictionary.Value)
     .Invoke(null, new[] { context, parent, context.Options, comparands.Subject, comparands.Expectation });
 }
        /// <summary>
        /// Asserts that an instance of <see cref="DataSet"/> is equivalent to another.
        /// </summary>
        /// <remarks>
        /// Data sets are equivalent when their <see cref="DataSet.Tables"/> and <see cref="DataSet.ExtendedProperties"/>
        /// collections are equivalent and the following members have the same values:
        ///
        /// <list type="bullet">
        ///   <item><description>DataSetName</description></item>
        ///   <item><description>CaseSensitive</description></item>
        ///   <item><description>EnforceConstraints</description></item>
        ///   <item><description>HasErrors</description></item>
        ///   <item><description>Locale</description></item>
        ///   <item><description>Namespace</description></item>
        ///   <item><description>Prefix</description></item>
        ///   <item><description>RemotingFormat</description></item>
        ///   <item><description>SchemaSerializationMode</description></item>
        /// </list>
        ///
        /// The <see cref="DataSet"/> objects must be of the same type; if two <see cref="DataSet"/> objects
        /// are equivalent in all ways, except that one is a custom subclass of <see cref="DataSet"/> (e.g. to provide
        /// typed accessors for <see cref="DataTable"/> values contained by the <see cref="DataSet"/>), then by default,
        /// they will not be considered equivalent.
        ///
        /// This, as well as testing of any property can be overridden using the <paramref name="config"/> callback.
        /// By calling <see cref="IDataEquivalencyAssertionOptions{T}.AllowingMismatchedTypes"/>, two <see cref="DataSet"/>
        /// objects of differing types can be considered equivalent. This setting applies to all types recursively tested
        /// as part of the <see cref="DataSet"/>.
        ///
        /// Exclude specific properties using <see cref="IDataEquivalencyAssertionOptions{T}.Excluding(System.Linq.Expressions.Expression{Func{T, object}})"/>.
        /// Exclude specific tables within the data set using <see cref="IDataEquivalencyAssertionOptions{T}.ExcludingTable(string)"/>
        /// or a related function. You can also indicate that columns should be excluded within the <see cref="DataTable"/>
        /// objects recursively tested as part of the <see cref="DataSet"/> using <see cref="IDataEquivalencyAssertionOptions{T}.ExcludingColumn(DataColumn)"/>
        /// or a related function. The <see cref="IDataEquivalencyAssertionOptions{T}.ExcludingColumnInAllTables(string)"/> method
        /// can be used to exclude columns across all <see cref="DataTable"/> objects in the <see cref="DataSet"/> that share
        /// the same name.
        ///
        /// You can use <see cref="IDataEquivalencyAssertionOptions{T}.ExcludingRelated(System.Linq.Expressions.Expression{Func{DataTable, object}})"/>
        /// and related functions to exclude properties on other related System.Data types.
        /// </remarks>
        /// <param name="expectation">A <see cref="DataColumn"/> with the expected configuration.</param>
        /// <param name="config">
        /// A reference to the <see cref="IDataEquivalencyAssertionOptions{DataSet}"/> configuration object that can be used
        /// to influence the way the object graphs are compared. You can also provide an alternative instance of the
        /// <see cref="IDataEquivalencyAssertionOptions{DataSet}"/> class. The global defaults are determined by the
        /// <see cref="AssertionOptions"/> class.
        /// </param>
        /// <param name="because">
        /// A formatted phrase as is supported by <see cref="string.Format(string,object[])" /> explaining why the assertion
        /// is needed. If the phrase does not start with the word <i>because</i>, it is prepended automatically.
        /// </param>
        /// <param name="becauseArgs">
        /// Zero or more objects to format using the placeholders in <paramref name="because"/>.
        /// </param>
        public AndConstraint <DataSetAssertions <TDataSet> > BeEquivalentTo(DataSet expectation, Func <IDataEquivalencyAssertionOptions <DataSet>, IDataEquivalencyAssertionOptions <DataSet> > config, string because = "", params object[] becauseArgs)
        {
            Guard.ThrowIfArgumentIsNull(config, nameof(config));

            IDataEquivalencyAssertionOptions <DataSet> options = config(AssertionOptions.CloneDefaults <DataSet, DataEquivalencyAssertionOptions <DataSet> >(e => new(e)));

            var comparands = new Comparands
            {
                Subject         = Subject,
                Expectation     = expectation,
                CompileTimeType = typeof(TDataSet)
            };

            var context = new EquivalencyValidationContext(Node.From <DataSet>(() => AssertionScope.Current.CallerIdentity), options)
            {
                Reason      = new Reason(because, becauseArgs),
                TraceWriter = options.TraceWriter,
            };

            var equivalencyValidator = new EquivalencyValidator();

            equivalencyValidator.AssertEquality(comparands, context);

            return(new AndConstraint <DataSetAssertions <TDataSet> >(this));
        }
Example #5
0
        protected override EquivalencyResult OnHandle(Comparands comparands, IEquivalencyValidationContext context, IEquivalencyValidator nestedValidator)
        {
            var subject     = comparands.Subject as IDictionary;
            var expectation = comparands.Expectation as IDictionary;

            if (PreconditionsAreMet(expectation, subject))
            {
                if (expectation is not null)
                {
                    foreach (object key in expectation.Keys)
                    {
                        if (context.Options.IsRecursive)
                        {
                            context.Tracer.WriteLine(member => Invariant($"Recursing into dictionary item {key} at {member.Description}"));
                            nestedValidator.RecursivelyAssertEquality(new Comparands(subject[key], expectation[key], typeof(object)), context.AsDictionaryItem <object, IDictionary>(key));
                        }
                        else
                        {
                            context.Tracer.WriteLine(member =>
                                                     Invariant($"Comparing dictionary item {key} at {member.Description} between subject and expectation"));
                            subject[key].Should().Be(expectation[key], context.Reason.FormattedMessage, context.Reason.Arguments);
                        }
                    }
                }
            }

            return(EquivalencyResult.AssertionCompleted);
        }
Example #6
0
        protected override EquivalencyResult OnHandle(Comparands comparands, IEquivalencyValidationContext context, IEquivalencyValidator nestedValidator)
        {
            var subject     = comparands.Subject as DataColumn;
            var expectation = comparands.Expectation as DataColumn;

            if (expectation is null)
            {
                if (subject is not null)
                {
                    AssertionScope.Current.FailWith("Expected {context:DataColumn} value to be null, but found {0}", subject);
                }
            }
            else
            {
                if (subject is null)
                {
                    if (comparands.Subject is null)
                    {
                        AssertionScope.Current.FailWith("Expected {context:DataColumn} to be non-null, but found null");
                    }
                    else
                    {
                        AssertionScope.Current.FailWith("Expected {context:DataColumn} to be of type {0}, but found {1} instead",
                                                        expectation.GetType(), comparands.Subject.GetType());
                    }
                }
                else
                {
                    CompareSubjectAndExpectationOfTypeDataColumn(comparands, context, nestedValidator, subject);
                }
            }

            return(EquivalencyResult.AssertionCompleted);
        }
Example #7
0
        private static void CompareSubjectAndExpectationOfTypeDataColumn(Comparands comparands,
                                                                         IEquivalencyValidationContext context, IEquivalencyValidator parent, DataColumn subject)
        {
            bool compareColumn = true;

            var dataSetConfig    = context.Options as DataEquivalencyAssertionOptions <DataSet>;
            var dataTableConfig  = context.Options as DataEquivalencyAssertionOptions <DataTable>;
            var dataColumnConfig = context.Options as DataEquivalencyAssertionOptions <DataColumn>;

            if ((dataSetConfig?.ShouldExcludeColumn(subject) == true) ||
                (dataTableConfig?.ShouldExcludeColumn(subject) == true) ||
                (dataColumnConfig?.ShouldExcludeColumn(subject) == true))
            {
                compareColumn = false;
            }

            if (compareColumn)
            {
                foreach (IMember expectationMember in GetMembersFromExpectation(context.CurrentNode, comparands, context.Options))
                {
                    if (expectationMember.Name != nameof(subject.Table))
                    {
                        CompareMember(expectationMember, comparands, parent, context);
                    }
                }
            }
        }
Example #8
0
 public ObjectInfo(Comparands comparands, INode currentNode)
 {
     Type            = currentNode.Type;
     Path            = currentNode.PathAndName;
     CompileTimeType = comparands.CompileTimeType;
     RuntimeType     = comparands.RuntimeType;
 }
        public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, IEquivalencyValidator nestedValidator)
        {
            Type expectationType = comparands.GetExpectedType(context.Options);

            if (expectationType is null || expectationType != typeof(string))
            {
                return(EquivalencyResult.ContinueWithNext);
            }

            if (!ValidateAgainstNulls(comparands, context.CurrentNode))
            {
                return(EquivalencyResult.AssertionCompleted);
            }

            bool subjectIsString = ValidateSubjectIsString(comparands, context.CurrentNode);

            if (subjectIsString)
            {
                string subject     = (string)comparands.Subject;
                string expectation = (string)comparands.Expectation;

                subject.Should()
                .Be(expectation, context.Reason.FormattedMessage, context.Reason.Arguments);
            }

            return(EquivalencyResult.AssertionCompleted);
        }
Example #10
0
            public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, IEquivalencyValidator nestedValidator)
            {
                if (comparands.Expectation is DateTime time)
                {
                    throw new Exception("Failed");
                }

                return(EquivalencyResult.ContinueWithNext);
            }
 internal static AssertionContext <TSubject> CreateFrom(Comparands comparands, IEquivalencyValidationContext context)
 {
     return(new(
                context.CurrentNode,
                (TSubject)comparands.Subject,
                (TSubject)comparands.Expectation,
                context.Reason.FormattedMessage,
                context.Reason.Arguments));
 }
        public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, IEquivalencyValidator nestedValidator)
        {
            if (!typeof(T).IsAssignableFrom(comparands.GetExpectedType(context.Options)))
            {
                return(EquivalencyResult.ContinueWithNext);
            }

            return(OnHandle(comparands, context, nestedValidator));
        }
Example #13
0
        protected override EquivalencyResult OnHandle(Comparands comparands, IEquivalencyValidationContext context, IEquivalencyValidator nestedValidator)
        {
            var subject     = (XDocument)comparands.Subject;
            var expectation = (XDocument)comparands.Expectation;

            subject.Should().BeEquivalentTo(expectation, context.Reason.FormattedMessage, context.Reason.Arguments);

            return(EquivalencyResult.AssertionCompleted);
        }
Example #14
0
            public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, IEquivalencyValidator nestedValidator)
            {
                if (comparands.Expectation is DateTime time)
                {
                    ((DateTime)comparands.Subject).Should().BeCloseTo(time, 1.Minutes());

                    return(EquivalencyResult.AssertionCompleted);
                }

                return(EquivalencyResult.ContinueWithNext);
            }
        protected override EquivalencyResult OnHandle(Comparands comparands, IEquivalencyValidationContext context, IEquivalencyValidator nestedValidator)
        {
            var subject = comparands.Subject as DataSet;

            if (comparands.Expectation is not DataSet expectation)
            {
                if (subject is not null)
                {
                    AssertionScope.Current.FailWith("Expected {context:DataSet} value to be null, but found {0}", subject);
                }
            }
        public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, IEquivalencyValidator nestedValidator)
        {
            if (!context.Options.IsRecursive && !context.CurrentNode.IsRoot)
            {
                comparands.Subject.Should().Be(comparands.Expectation, context.Reason.FormattedMessage, context.Reason.Arguments);

                return(EquivalencyResult.AssertionCompleted);
            }

            return(EquivalencyResult.ContinueWithNext);
        }
        private static bool ValidateSubjectIsString(Comparands comparands, INode currentNode)
        {
            if (comparands.Subject is string)
            {
                return(true);
            }

            return
                (AssertionScope.Current
                 .FailWith($"Expected {currentNode} to be {{0}}, but found {{1}}.",
                           comparands.RuntimeType, comparands.Subject.GetType()));
        }
        public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context, IEquivalencyValidator nestedValidator)
        {
            foreach (IEquivalencyStep step in context.Options.UserEquivalencySteps)
            {
                if (step.Handle(comparands, context, nestedValidator) == EquivalencyResult.AssertionCompleted)
                {
                    return(EquivalencyResult.AssertionCompleted);
                }
            }

            return(EquivalencyResult.ContinueWithNext);
        }
Example #19
0
        protected override EquivalencyResult OnHandle(Comparands comparands, IEquivalencyValidationContext context, IEquivalencyValidator nestedValidator)
        {
            var subject     = comparands.Subject as DataRow;
            var expectation = comparands.Expectation as DataRow;

            if (expectation is null)
            {
                if (subject is not null)
                {
                    AssertionScope.Current.FailWith("Expected {context:DataRow} value to be null, but found {0}", subject);
                }
            }
            else
            {
                if (subject is null)
                {
                    if (comparands.Subject is null)
                    {
                        AssertionScope.Current.FailWith("Expected {context:DataRow} to be non-null, but found null");
                    }
                    else
                    {
                        AssertionScope.Current.FailWith("Expected {context:DataRow} to be of type {0}, but found {1} instead",
                                                        expectation.GetType(), comparands.Subject.GetType());
                    }
                }
                else
                {
                    var dataSetConfig   = context.Options as DataEquivalencyAssertionOptions <DataSet>;
                    var dataTableConfig = context.Options as DataEquivalencyAssertionOptions <DataTable>;
                    var dataRowConfig   = context.Options as DataEquivalencyAssertionOptions <DataRow>;

                    if (dataSetConfig?.AllowMismatchedTypes != true &&
                        dataTableConfig?.AllowMismatchedTypes != true &&
                        dataRowConfig?.AllowMismatchedTypes != true)
                    {
                        AssertionScope.Current
                        .ForCondition(subject.GetType() == expectation.GetType())
                        .FailWith("Expected {context:DataRow} to be of type '{0}'{reason}, but found '{1}'",
                                  expectation.GetType(), subject.GetType());
                    }

                    SelectedDataRowMembers selectedMembers = GetMembersFromExpectation(comparands, context.CurrentNode, context.Options);

                    CompareScalarProperties(subject, expectation, selectedMembers);

                    CompareFieldValues(context, nestedValidator, subject, expectation, dataSetConfig, dataTableConfig,
                                       dataRowConfig);
                }
            }

            return(EquivalencyResult.AssertionCompleted);
        }
Example #20
0
        protected override EquivalencyResult OnHandle(Comparands comparands, IEquivalencyValidationContext context, IEquivalencyValidator nestedValidator)
        {
            if (comparands.Subject is not DataRowCollection)
            {
                AssertionScope.Current
                .FailWith("Expected {context:value} to be of type DataRowCollection, but found {0}",
                          comparands.Subject.GetType());
            }
            else
            {
                RowMatchMode rowMatchMode = RowMatchMode.Index;

                if (context.Options is DataEquivalencyAssertionOptions <DataSet> dataSetConfig)
                {
                    rowMatchMode = dataSetConfig.RowMatchMode;
                }
                else if (context.Options is DataEquivalencyAssertionOptions <DataTable> dataTableConfig)
                {
                    rowMatchMode = dataTableConfig.RowMatchMode;
                }

                var subject     = (DataRowCollection)comparands.Subject;
                var expectation = (DataRowCollection)comparands.Expectation;

                bool success = AssertionScope.Current
                               .ForCondition(subject.Count == expectation.Count)
                               .FailWith("Expected {context:DataRowCollection} to contain {0} row(s){reason}, but found {1}",
                                         expectation.Count, subject.Count);

                if (success)
                {
                    switch (rowMatchMode)
                    {
                    case RowMatchMode.Index:
                        MatchRowsByIndexAndCompare(context, nestedValidator, subject, expectation);
                        break;

                    case RowMatchMode.PrimaryKey:
                        MatchRowsByPrimaryKeyAndCompare(nestedValidator, context, subject, expectation);
                        break;

                    default:
                        AssertionScope.Current.FailWith(
                            "Unknown RowMatchMode {0} when trying to compare {context:DataRowCollection}", rowMatchMode);
                        break;
                    }
                }
            }

            return(EquivalencyResult.AssertionCompleted);
        }
        private static IEnumerable <IMember> GetMembersFromExpectation(Comparands comparands,
                                                                       INode contextCurrentNode,
                                                                       IEquivalencyAssertionOptions options)
        {
            IEnumerable <IMember> members = Enumerable.Empty <IMember>();

            foreach (IMemberSelectionRule rule in options.SelectionRules)
            {
                members = rule.SelectMembers(contextCurrentNode, members,
                                             new MemberSelectionContext(comparands.CompileTimeType, comparands.RuntimeType, options));
            }

            return(members);
        }
Example #22
0
        private static bool AssertSameLength(Comparands comparands, DictionaryInterfaceInfo actualDictionary,
                                             DictionaryInterfaceInfo expectedDictionary)
        {
            if (comparands.Subject is ICollection subjectCollection &&
                comparands.Expectation is ICollection expectationCollection &&
                subjectCollection.Count == expectationCollection.Count)
            {
                return(true);
            }

            return((bool)AssertSameLengthMethod
                   .MakeGenericMethod(actualDictionary.Key, actualDictionary.Value, expectedDictionary.Key, expectedDictionary.Value)
                   .Invoke(null, new[] { comparands.Subject, comparands.Expectation }));
        }
Example #23
0
    public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context,
                                    IEquivalencyValidator nestedValidator)
    {
        var canHandle = comparands.Subject?.GetType().IsAssignableTo(typeof(JToken)) ?? false;

        if (!canHandle)
        {
            return(EquivalencyResult.ContinueWithNext);
        }

        ((JToken)comparands.Subject !).Should().BeEquivalentTo(
            (JToken)comparands.Expectation, context.Reason.FormattedMessage, context.Reason.Arguments);

        return(EquivalencyResult.AssertionCompleted);
    }
        public void When_stringifying_a_validation_context_it_should_ignore_culture()
        {
            // Arrange
            var comparands = new Comparands
            {
                Subject     = 1.234,
                Expectation = 5.678
            };

            // Act
            var str = comparands.ToString();

            // Assert
            str.Should().Match("*1.234*5.678*", "it should always use . as decimal separator");
        }
Example #25
0
        private static void HandleByValue(Comparands comparands)
        {
            decimal?subjectsUnderlyingValue     = ExtractDecimal(comparands.Subject);
            decimal?expectationsUnderlyingValue = ExtractDecimal(comparands.Expectation);

            Execute.Assertion
            .ForCondition(subjectsUnderlyingValue == expectationsUnderlyingValue)
            .FailWith(() =>
            {
                string subjectsName    = GetDisplayNameForEnumComparison(comparands.Subject, subjectsUnderlyingValue);
                string expectationName = GetDisplayNameForEnumComparison(comparands.Expectation, expectationsUnderlyingValue);

                return(new FailReason($"Expected {{context:enum}} to equal {expectationName} by value{{reason}}, but found {subjectsName}."));
            });
        }
        private static bool ValidateAgainstNulls(Comparands comparands, INode currentNode)
        {
            object expected = comparands.Expectation;
            object subject  = comparands.Subject;

            bool onlyOneNull = (expected is null) != (subject is null);

            if (onlyOneNull)
            {
                AssertionScope.Current.FailWith(
                    $"Expected {currentNode.Description} to be {{0}}{{reason}}, but found {{1}}.", expected, subject);

                return(false);
            }

            return(true);
        }
        private static void AssertMemberEquality(Comparands comparands, IEquivalencyValidationContext context,
                                                 IEquivalencyValidator parent, IMember selectedMember, IEquivalencyAssertionOptions options)
        {
            IMember matchingMember = FindMatchFor(selectedMember, context.CurrentNode, comparands.Subject, options);

            if (matchingMember is not null)
            {
                var nestedComparands = new Comparands
                {
                    Subject         = matchingMember.GetValue(comparands.Subject),
                    Expectation     = selectedMember.GetValue(comparands.Expectation),
                    CompileTimeType = selectedMember.Type
                };

                parent.RecursivelyAssertEquality(nestedComparands, context.AsNestedMember(selectedMember));
            }
        }
Example #28
0
#pragma warning restore SA1110

        public EquivalencyResult Handle(Comparands comparands, IEquivalencyValidationContext context,
                                        IEquivalencyValidator nestedValidator)
        {
            if (comparands.Expectation != null)
            {
                Type expectationType = comparands.GetExpectedType(context.Options);
                bool isDictionary    = DictionaryInterfaceInfo.TryGetFrom(expectationType, "expectation", out var expectedDictionary);
                if (isDictionary)
                {
                    Handle(comparands, expectedDictionary, context, nestedValidator);

                    return(EquivalencyResult.AssertionCompleted);
                }
            }

            return(EquivalencyResult.ContinueWithNext);
        }
Example #29
0
 private static void Handle(Comparands comparands, DictionaryInterfaceInfo expectedDictionary,
                            IEquivalencyValidationContext context,
                            IEquivalencyValidator nestedValidator)
 {
     if (AssertSubjectIsNotNull(comparands.Subject) &&
         AssertExpectationIsNotNull(comparands.Subject, comparands.Expectation))
     {
         var(isDictionary, actualDictionary) = EnsureSubjectIsDictionary(comparands, expectedDictionary);
         if (isDictionary)
         {
             if (AssertSameLength(comparands, actualDictionary, expectedDictionary))
             {
                 AssertDictionaryEquivalence(comparands, context, nestedValidator, actualDictionary, expectedDictionary);
             }
         }
     }
 }
Example #30
0
        protected override EquivalencyResult OnHandle(Comparands comparands, IEquivalencyValidationContext context, IEquivalencyValidator nestedValidator)
        {
            if (comparands.Subject is not ConstraintCollection)
            {
                AssertionScope.Current
                .FailWith("Expected a value of type ConstraintCollection at {context:Constraints}, but found {0}", comparands.Subject.GetType());
            }
            else
            {
                var subject     = (ConstraintCollection)comparands.Subject;
                var expectation = (ConstraintCollection)comparands.Expectation;

                var subjectConstraints     = subject.Cast <Constraint>().ToDictionary(constraint => constraint.ConstraintName);
                var expectationConstraints = expectation.Cast <Constraint>().ToDictionary(constraint => constraint.ConstraintName);

                IEnumerable <string> constraintNames = subjectConstraints.Keys.Union(expectationConstraints.Keys);

                foreach (var constraintName in constraintNames)
                {
                    AssertionScope.Current
                    .ForCondition(subjectConstraints.TryGetValue(constraintName, out Constraint subjectConstraint))
                    .FailWith("Expected constraint named {0} in {context:Constraints collection}{reason}, but did not find one", constraintName);

                    AssertionScope.Current
                    .ForCondition(expectationConstraints.TryGetValue(constraintName, out Constraint expectationConstraint))
                    .FailWith("Found unexpected constraint named {0} in {context:Constraints collection}", constraintName);

                    if ((subjectConstraint is not null) && (expectationConstraint is not null))
                    {
                        Comparands newComparands = new()
                        {
                            Subject         = subjectConstraint,
                            Expectation     = expectationConstraint,
                            CompileTimeType = typeof(Constraint)
                        };

                        IEquivalencyValidationContext nestedContext = context.AsCollectionItem <Constraint>(constraintName);
                        nestedValidator.RecursivelyAssertEquality(newComparands, nestedContext);
                    }
                }
            }

            return(EquivalencyResult.AssertionCompleted);
        }
    }