Skip to content

Commit 5e6d9ee

Browse files
committed
Decoupled the EquivalencyValidatorContext from the actual configuration.
Better handled the cyclic reference detection in nested assertion scopes.
1 parent 5f74099 commit 5e6d9ee

17 files changed

+278
-149
lines changed

Main/FluentAssertions.Net35/AssertionExtensions.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -606,7 +606,7 @@ public static void ShouldBeEquivalentTo<T>(this T subject, object expectation, s
606606
public static void ShouldBeEquivalentTo<T>(this T subject, object expectation,
607607
Func<EquivalencyAssertionOptions<T>, EquivalencyAssertionOptions<T>> config, string reason = "", params object[] reasonArgs)
608608
{
609-
var context = new EquivalencyValidationContext(config(EquivalencyAssertionOptions<T>.Default()))
609+
var context = new EquivalencyValidationContext
610610
{
611611
Subject = subject,
612612
Expectation = expectation,
@@ -615,7 +615,7 @@ public static void ShouldBeEquivalentTo<T>(this T subject, object expectation,
615615
ReasonArgs = reasonArgs
616616
};
617617

618-
new EquivalencyValidator().AssertEquality(context);
618+
new EquivalencyValidator(config(EquivalencyAssertionOptions<T>.Default())).AssertEquality(context);
619619
}
620620

621621
public static void ShouldAllBeEquivalentTo<T>(this IEnumerable<T> subject, IEnumerable expectation,
@@ -627,7 +627,7 @@ public static void ShouldAllBeEquivalentTo<T>(this IEnumerable<T> subject, IEnum
627627
public static void ShouldAllBeEquivalentTo<T>(this IEnumerable<T> subject, IEnumerable expectation,
628628
Func<EquivalencyAssertionOptions<T>, EquivalencyAssertionOptions<T>> config, string reason = "", params object[] reasonArgs)
629629
{
630-
var context = new EquivalencyValidationContext(config(EquivalencyAssertionOptions<T>.Default()))
630+
var context = new EquivalencyValidationContext()
631631
{
632632
Subject = subject,
633633
Expectation = expectation,
@@ -636,7 +636,7 @@ public static void ShouldAllBeEquivalentTo<T>(this IEnumerable<T> subject, IEnum
636636
ReasonArgs = reasonArgs
637637
};
638638

639-
new EquivalencyValidator().AssertEquality(context);
639+
new EquivalencyValidator(config(EquivalencyAssertionOptions<T>.Default())).AssertEquality(context);
640640
}
641641

642642

Main/FluentAssertions.Net35/Equivalency/ApplyAssertionRulesEquivalencyStep.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ public class ApplyAssertionRulesEquivalencyStep : IEquivalencyStep
77
/// <summary>
88
/// Gets a value indicating whether this step can handle the current subject and/or expectation.
99
/// </summary>
10-
public bool CanHandle(EquivalencyValidationContext context)
10+
public bool CanHandle(EquivalencyValidationContext context, IEquivalencyAssertionOptions config)
1111
{
1212
return true;
1313
}
@@ -22,11 +22,11 @@ public bool CanHandle(EquivalencyValidationContext context)
2222
/// <remarks>
2323
/// May throw when preconditions are not met or if it detects mismatching data.
2424
/// </remarks>
25-
public virtual bool Handle(EquivalencyValidationContext context, IEquivalencyValidator parent)
25+
public virtual bool Handle(EquivalencyValidationContext context, IEquivalencyValidator parent, IEquivalencyAssertionOptions config)
2626
{
2727
if (context.PropertyInfo != null)
2828
{
29-
return context.Config.AssertionRules.Any(rule => rule.AssertEquality(context));
29+
return config.AssertionRules.Any(rule => rule.AssertEquality(context));
3030
}
3131

3232
return false;

Main/FluentAssertions.Net35/Equivalency/ComplexTypeEquivalencyStep.cs

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ public class ComplexTypeEquivalencyStep : IEquivalencyStep
1212
/// <summary>
1313
/// Gets a value indicating whether this step can handle the current subject and/or expectation.
1414
/// </summary>
15-
public bool CanHandle(EquivalencyValidationContext context)
15+
public bool CanHandle(EquivalencyValidationContext context, IEquivalencyAssertionOptions config)
1616
{
1717
return (context.Subject != null) &&
18-
context.Subject.GetType().IsComplexType() && (context.IsRoot || context.Config.IsRecursive);
18+
context.Subject.GetType().IsComplexType() && (context.IsRoot || config.IsRecursive);
1919
}
2020

2121
/// <summary>
@@ -28,30 +28,57 @@ public bool CanHandle(EquivalencyValidationContext context)
2828
/// <remarks>
2929
/// May throw when preconditions are not met or if it detects mismatching data.
3030
/// </remarks>
31-
public virtual bool Handle(EquivalencyValidationContext context, IEquivalencyValidator parent)
31+
public virtual bool Handle(EquivalencyValidationContext context, IEquivalencyValidator parent, IEquivalencyAssertionOptions config)
3232
{
33-
IEnumerable<PropertyInfo> selectedProperties = context.SelectedProperties.ToArray();
33+
IEnumerable<PropertyInfo> selectedProperties = GetSelectedProperties(context, config).ToArray();
3434
if (context.IsRoot && !selectedProperties.Any())
3535
{
3636
throw new InvalidOperationException("Please specify some properties to include in the comparison.");
3737
}
3838

3939
foreach (PropertyInfo propertyInfo in selectedProperties)
4040
{
41-
AssertPropertyEquality(context, parent, propertyInfo);
41+
AssertPropertyEquality(context, parent, propertyInfo, config);
4242
}
4343

4444
return true;
4545
}
4646

47-
private void AssertPropertyEquality(EquivalencyValidationContext context, IEquivalencyValidator parent,
48-
PropertyInfo propertyInfo)
47+
private void AssertPropertyEquality(EquivalencyValidationContext context, IEquivalencyValidator parent, PropertyInfo propertyInfo, IEquivalencyAssertionOptions config)
4948
{
50-
EquivalencyValidationContext nestedContext = context.CreateForNestedProperty(propertyInfo);
51-
if (nestedContext != null)
49+
var matchingProperty = FindMatchFor(propertyInfo, context, config.MatchingRules);
50+
if (matchingProperty != null)
5251
{
53-
parent.AssertEqualityUsing(nestedContext);
52+
EquivalencyValidationContext nestedContext = context.CreateForNestedProperty(propertyInfo, matchingProperty);
53+
if (nestedContext != null)
54+
{
55+
parent.AssertEqualityUsing(nestedContext);
56+
}
5457
}
5558
}
59+
60+
private PropertyInfo FindMatchFor(PropertyInfo propertyInfo, EquivalencyValidationContext context, IEnumerable<IMatchingRule> matchingRules)
61+
{
62+
var query =
63+
from rule in matchingRules
64+
let match = rule.Match(propertyInfo, context.Expectation, context.PropertyDescription)
65+
where match != null
66+
select match;
67+
68+
return query.FirstOrDefault();
69+
}
70+
71+
internal IEnumerable<PropertyInfo> GetSelectedProperties(EquivalencyValidationContext context,
72+
IEquivalencyAssertionOptions config)
73+
{
74+
IEnumerable<PropertyInfo> properties = new List<PropertyInfo>();
75+
76+
foreach (ISelectionRule selectionRule in config.SelectionRules)
77+
{
78+
properties = selectionRule.SelectProperties(properties, context);
79+
}
80+
81+
return properties;
82+
}
5683
}
5784
}

Main/FluentAssertions.Net35/Equivalency/DictionaryEquivalencyStep.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ public class DictionaryEquivalencyStep : IEquivalencyStep
99
/// <summary>
1010
/// Gets a value indicating whether this step can handle the current subject and/or expectation.
1111
/// </summary>
12-
public bool CanHandle(EquivalencyValidationContext context)
12+
public bool CanHandle(EquivalencyValidationContext context, IEquivalencyAssertionOptions config)
1313
{
1414
return (context.Subject is IDictionary);
1515
}
@@ -24,7 +24,7 @@ public bool CanHandle(EquivalencyValidationContext context)
2424
/// <remarks>
2525
/// May throw when preconditions are not met or if it detects mismatching data.
2626
/// </remarks>
27-
public virtual bool Handle(EquivalencyValidationContext context, IEquivalencyValidator parent)
27+
public virtual bool Handle(EquivalencyValidationContext context, IEquivalencyValidator parent, IEquivalencyAssertionOptions config)
2828
{
2929
var subject = (IDictionary)context.Subject;
3030
var expectation = context.Expectation as IDictionary;
@@ -33,7 +33,7 @@ public virtual bool Handle(EquivalencyValidationContext context, IEquivalencyVal
3333
{
3434
foreach (object key in subject.Keys)
3535
{
36-
if (context.Config.IsRecursive)
36+
if (config.IsRecursive)
3737
{
3838
parent.AssertEqualityUsing(context.CreateForDictionaryItem(key, subject[key], expectation[key]));
3939
}

Main/FluentAssertions.Net35/Equivalency/EnumerableEquivalencyStep.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Collections;
22
using System.Linq;
3+
34
using FluentAssertions.Execution;
45

56
namespace FluentAssertions.Equivalency
@@ -9,7 +10,7 @@ public class EnumerableEquivalencyStep : IEquivalencyStep
910
/// <summary>
1011
/// Gets a value indicating whether this step can handle the verificationScope subject and/or expectation.
1112
/// </summary>
12-
public bool CanHandle(EquivalencyValidationContext context)
13+
public bool CanHandle(EquivalencyValidationContext context, IEquivalencyAssertionOptions config)
1314
{
1415
return IsCollection(context.Subject);
1516
}
@@ -24,13 +25,13 @@ public bool CanHandle(EquivalencyValidationContext context)
2425
/// <remarks>
2526
/// May throw when preconditions are not met or if it detects mismatching data.
2627
/// </remarks>
27-
public bool Handle(EquivalencyValidationContext context, IEquivalencyValidator parent)
28+
public bool Handle(EquivalencyValidationContext context, IEquivalencyValidator parent, IEquivalencyAssertionOptions config)
2829
{
2930
if (AssertExpectationIsCollection(context.Expectation))
3031
{
3132
var validator = new EnumerableEquivalencyValidator(parent, context)
3233
{
33-
Recursive = context.IsRoot || context.Config.IsRecursive
34+
Recursive = context.IsRoot || config.IsRecursive
3435
};
3536

3637
validator.Validate(ToArray(context.Subject), ToArray(context.Expectation));
@@ -53,7 +54,7 @@ private static bool IsCollection(object value)
5354

5455
private object[] ToArray(object value)
5556
{
56-
return ((IEnumerable) value).Cast<object>().ToArray();
57+
return ((IEnumerable)value).Cast<object>().ToArray();
5758
}
5859
}
5960

@@ -125,11 +126,11 @@ private void LooselyMatchAgainst(object[] subjects, object expectation, int expe
125126
}
126127
}
127128

128-
private string[] TryToMatch(object subject, object expectation, int index)
129+
private string[] TryToMatch(object subject, object expectation, int expectationIndex)
129130
{
130131
using (var scope = new AssertionScope())
131132
{
132-
parent.AssertEqualityUsing(context.CreateForCollectionItem(index, subject, expectation));
133+
parent.AssertEqualityUsing(context.CreateForCollectionItem(expectationIndex, subject, expectation));
133134

134135
return scope.Discard();
135136
}

Main/FluentAssertions.Net35/Equivalency/EquivalencyValidationContext.cs

Lines changed: 12 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,18 @@
11
using System;
2-
using System.Collections.Generic;
3-
using System.Linq;
42
using System.Reflection;
53

64
using FluentAssertions.Common;
7-
using FluentAssertions.Execution;
85

96
namespace FluentAssertions.Equivalency
107
{
118
public class EquivalencyValidationContext : IEquivalencyValidationContext
129
{
13-
#region Private Definitions
14-
15-
private IList<object> processedObjects = new List<object>();
16-
17-
#endregion
18-
19-
public EquivalencyValidationContext(IEquivalencyAssertionOptions config)
10+
public EquivalencyValidationContext()
2011
{
21-
Config = config;
2212
PropertyDescription = "";
2313
PropertyPath = "";
2414
}
2515

26-
public IEquivalencyAssertionOptions Config { get; private set; }
27-
2816
/// <summary>
2917
/// Gets the <see cref="ISubjectInfo.PropertyInfo" /> of the property that returned the current object, or
3018
/// <c>null</c> if the current object represents the root object.
@@ -99,83 +87,31 @@ public Type RuntimeType
9987
{
10088
type = PropertyInfo.PropertyType;
10189
}
102-
else
103-
{
104-
// Default
105-
}
10690

10791
return type;
10892
}
10993
}
11094

111-
internal bool ContainsCyclicReference
112-
{
113-
get { return processedObjects.Contains(Subject); }
114-
}
115-
116-
internal IEnumerable<PropertyInfo> SelectedProperties
117-
{
118-
get
119-
{
120-
IEnumerable<PropertyInfo> properties = new List<PropertyInfo>();
121-
122-
foreach (ISelectionRule selectionRule in Config.SelectionRules)
123-
{
124-
properties = selectionRule.SelectProperties(properties, this);
125-
}
126-
127-
return properties;
128-
}
129-
}
130-
131-
internal void HandleCyclicReference()
132-
{
133-
if (Config.CyclicReferenceHandling == CyclicReferenceHandling.ThrowException)
134-
{
135-
Execute.Assertion
136-
.BecauseOf(Reason, ReasonArgs)
137-
.FailWith(
138-
"Expected " + PropertyDescription + " to be {0}{reason}, but it contains a cyclic reference.",
139-
Expectation);
140-
}
141-
}
142-
143-
internal EquivalencyValidationContext CreateForNestedProperty(PropertyInfo nestedProperty)
144-
{
145-
EquivalencyValidationContext nestedContext = null;
146-
147-
var matchingProperty = FindMatchFor(nestedProperty);
148-
if (matchingProperty != null)
149-
{
150-
var subject = nestedProperty.GetValue(Subject, null);
151-
var expectation = matchingProperty.GetValue(Expectation, null);
152-
153-
nestedContext = CreateNested(nestedProperty, subject, matchingProperty, expectation, "property ",
154-
nestedProperty.Name, ".");
155-
}
156-
157-
return nestedContext;
158-
}
159-
160-
private PropertyInfo FindMatchFor(PropertyInfo propertyInfo)
95+
internal EquivalencyValidationContext CreateForNestedProperty(PropertyInfo nestedProperty,
96+
PropertyInfo matchingProperty)
16197
{
162-
var query =
163-
from rule in Config.MatchingRules
164-
let match = rule.Match(propertyInfo, Expectation, PropertyDescription)
165-
where match != null
166-
select match;
98+
object subject = nestedProperty.GetValue(Subject, null);
99+
object expectation = matchingProperty.GetValue(Expectation, null);
167100

168-
return query.FirstOrDefault();
101+
return CreateNested(nestedProperty, subject, matchingProperty, expectation, "property ",
102+
nestedProperty.Name, ".");
169103
}
170104

171105
public EquivalencyValidationContext CreateForCollectionItem(int index, object subject, object expectation)
172106
{
173-
return CreateNested(PropertyInfo, subject, MatchingExpectationProperty, expectation, "item", "[" + index + "]", "");
107+
return CreateNested(PropertyInfo, subject, MatchingExpectationProperty, expectation, "item",
108+
"[" + index + "]", "");
174109
}
175110

176111
public EquivalencyValidationContext CreateForDictionaryItem(object key, object subject, object expectation)
177112
{
178-
return CreateNested(PropertyInfo, subject, MatchingExpectationProperty, expectation, "pair", "[" + key + "]", "");
113+
return CreateNested(PropertyInfo, subject, MatchingExpectationProperty, expectation, "pair", "[" + key + "]",
114+
"");
179115
}
180116

181117
private EquivalencyValidationContext CreateNested(
@@ -185,7 +121,7 @@ private EquivalencyValidationContext CreateNested(
185121
{
186122
string propertyPath = IsRoot ? memberType : PropertyDescription + separator;
187123

188-
return new EquivalencyValidationContext(Config)
124+
return new EquivalencyValidationContext
189125
{
190126
PropertyInfo = subjectProperty,
191127
MatchingExpectationProperty = matchingProperty,
@@ -196,7 +132,6 @@ private EquivalencyValidationContext CreateNested(
196132
Reason = Reason,
197133
ReasonArgs = ReasonArgs,
198134
CompileTimeType = (subject != null) ? subject.GetType() : null,
199-
processedObjects = processedObjects.Concat(new[] { Subject }).ToList()
200135
};
201136
}
202137
}

0 commit comments

Comments
 (0)