/
GenericEnumerableEquivalencyStep.cs
113 lines (92 loc) · 4.46 KB
/
GenericEnumerableEquivalencyStep.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using FluentAssertions.Common;
using FluentAssertions.Execution;
namespace FluentAssertions.Equivalency
{
public class GenericEnumerableEquivalencyStep : IEquivalencyStep
{
private static readonly MethodInfo HandleMethod = new Action<EnumerableEquivalencyValidator, object[], IEnumerable<object>>
(HandleImpl).GetMethodInfo().GetGenericMethodDefinition();
/// <summary>
/// Gets a value indicating whether this step can handle the verificationScope subject and/or expectation.
/// </summary>
public bool CanHandle(IEquivalencyValidationContext context, IEquivalencyAssertionOptions config)
{
var expectationType = config.GetExpectationType(context);
return (context.Expectation != null) && IsGenericCollection(expectationType);
}
/// <summary>
/// Applies a step as part of the task to compare two objects for structural equality.
/// </summary>
/// <value>
/// Should return <c>true</c> if the subject matches the expectation or if no additional assertions
/// have to be executed. Should return <c>false</c> otherwise.
/// </value>
/// <remarks>
/// May throw when preconditions are not met or if it detects mismatching data.
/// </remarks>
public bool Handle(IEquivalencyValidationContext context, IEquivalencyValidator parent,
IEquivalencyAssertionOptions config)
{
Type expectedType = config.GetExpectationType(context);
var interfaceTypes = GetIEnumerableInterfaces(expectedType);
AssertionScope.Current
.ForCondition(interfaceTypes.Length == 1)
.FailWith(() => new FailReason("{context:Expectation} implements {0}, so cannot determine which one " +
"to use for asserting the equivalency of the collection. ",
interfaceTypes.Select(type => "IEnumerable<" + type.GetGenericArguments().Single() + ">")));
if (AssertSubjectIsCollection(context.Expectation, context.Subject))
{
var validator = new EnumerableEquivalencyValidator(parent, context)
{
Recursive = context.IsRoot || config.IsRecursive,
OrderingRules = config.OrderingRules
};
Type typeOfEnumeration = GetTypeOfEnumeration(expectedType);
var subjectAsArray = EnumerableEquivalencyStep.ToArray(context.Subject);
try
{
HandleMethod.MakeGenericMethod(typeOfEnumeration).Invoke(null, new[] { validator, subjectAsArray, context.Expectation });
}
catch (TargetInvocationException e)
{
throw e.Unwrap();
}
}
return true;
}
private static void HandleImpl<T>(EnumerableEquivalencyValidator validator, object[] subject, IEnumerable<T> expectation)
=> validator.Execute(subject, expectation?.ToArray());
private static bool AssertSubjectIsCollection(object expectation, object subject)
{
bool conditionMet = AssertionScope.Current
.ForCondition(!(subject is null))
.FailWith("Expected {context:Subject} not to be {0}.", new object[] { null });
if (conditionMet)
{
conditionMet = AssertionScope.Current
.ForCondition(IsGenericCollection(subject.GetType()))
.FailWith("Expected {context:Subject} to be {0}, but found {1}.", expectation, subject);
}
return conditionMet;
}
private static bool IsGenericCollection(Type type)
{
var enumerableInterfaces = GetIEnumerableInterfaces(type);
return (!typeof(string).IsAssignableFrom(type)) && enumerableInterfaces.Any();
}
private static Type[] GetIEnumerableInterfaces(Type type)
{
Type soughtType = typeof(IEnumerable<>);
return Common.TypeExtensions.GetClosedGenericInterfaces(type, soughtType);
}
private static Type GetTypeOfEnumeration(Type enumerableType)
{
Type interfaceType = GetIEnumerableInterfaces(enumerableType).Single();
return interfaceType.GetGenericArguments().Single();
}
}
}