-
-
Notifications
You must be signed in to change notification settings - Fork 794
/
Match.cs
277 lines (246 loc) · 9.83 KB
/
Match.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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
// Copyright (c) 2007, Clarius Consulting, Manas Technology Solutions, InSTEDD, and Contributors.
// All rights reserved. Licensed under the BSD 3-Clause License; see License.txt.
using System;
using System.Diagnostics;
using System.Linq.Expressions;
using System.Reflection;
using Moq.Expressions.Visitors;
namespace Moq
{
/// <summary>
/// Allows creating custom value matchers that can be used on setups and verification,
/// completely replacing the built-in <see cref="It"/> class with your own
/// argument matching rules.
/// </summary>
/// <remarks>
/// Argument matching is used to determine whether a concrete invocation in the mock
/// matches a given setup. This matching mechanism is fully extensible.
/// </remarks>
/// <example>
/// Creating a custom matcher is straightforward. You just need to create a method
/// that returns a value from a call to <see cref="Match.Create{T}(Predicate{T})"/>
/// with your matching condition and optional friendly render expression:
/// <code>
/// public Order IsBigOrder()
/// {
/// return Match.Create<Order>(
/// o => o.GrandTotal >= 5000,
/// () => IsBigOrder()); // a friendly expression to render on failures
/// }
/// </code>
/// This method can be used in any mock setup invocation:
/// <code>
/// mock.Setup(m => m.Submit(IsBigOrder())
/// .Throws<UnauthorizedAccessException>();
/// </code>
/// At runtime, Moq knows that the return value was a matcher and
/// evaluates your predicate with the actual value passed into your predicate.
/// <para>
/// Another example might be a case where you want to match a lists of orders
/// that contains a particular one. You might create matcher like the following:
/// </para>
/// <code>
/// public static class Orders
/// {
/// public static IEnumerable<Order> Contains(Order order)
/// {
/// return Match.Create<IEnumerable<Order>>(orders => orders.Contains(order));
/// }
/// }
/// </code>
/// Now we can invoke this static method instead of an argument in an invocation:
/// <code>
/// var order = new Order { ... };
/// var mock = new Mock<IRepository<Order>>();
///
/// mock.Setup(x => x.Save(Orders.Contains(order)))
/// .Throws<ArgumentException>();
/// </code>
/// </example>
public abstract class Match : IMatcher
{
/// <devdoc>
/// Provided for the sole purpose of rendering the delegate passed to the
/// matcher constructor if no friendly render lambda is provided.
/// </devdoc>
internal static TValue Matcher<TValue>()
{
return default(TValue);
}
internal abstract bool Matches(object argument, Type parameterType);
internal abstract void SetupEvaluatedSuccessfully(object argument, Type parameterType);
bool IMatcher.Matches(object argument, Type parameterType) => this.Matches(argument, parameterType);
void IMatcher.SetupEvaluatedSuccessfully(object value, Type parameterType) => this.SetupEvaluatedSuccessfully(value, parameterType);
internal Expression RenderExpression { get; set; }
/// <summary>
/// Initializes the matcher with the condition that will be checked
/// in order to match invocation values.
/// </summary>
/// <param name="condition">The condition to match against actual values.</param>
public static T Create<T>(Predicate<T> condition)
{
Match.Register(new Match<T>(condition, () => Matcher<T>()));
return default(T);
}
/// <summary>
/// Initializes the matcher with the condition that will be checked
/// in order to match invocation values.
/// </summary>
/// <param name="condition">The condition to match against actual values.</param>
/// <param name="renderExpression">
/// A lambda representation of the matcher, to be used when rendering error messages,
/// such as <c>() => It.IsAny<string<()</c>.
/// </param>
public static T Create<T>(Predicate<T> condition, Expression<Func<T>> renderExpression)
{
Match.Register(new Match<T>(condition, renderExpression));
return default(T);
}
/// <summary>
/// Initializes the matcher with the condition that will be checked in order to match invocation values.
/// <para>
/// The <paramref name="condition"/> predicate of this overload will not only be provided with a
/// method argument, but also with the associated parameter's type. This parameter type essentially
/// overrides <typeparamref name="T"/> in cases where the latter is a type matcher. Therefore,
/// use this method overload if you want your custom matcher to work together with type matchers.
/// </para>
/// </summary>
/// <param name="condition">
/// The condition to match against actual values.
/// <para>
/// This function will be passed the invocation argument, as well as the type of the associated parameter.
/// </para>
/// </param>
/// <param name="renderExpression">
/// A lambda representation of the matcher.
/// </param>
public static T Create<T>(Func<object, Type, bool> condition, Expression<Func<T>> renderExpression)
{
Guard.NotNull(condition, nameof(condition));
Guard.NotNull(renderExpression, nameof(renderExpression));
Match.Register(new MatchFactory(condition, renderExpression));
return default(T);
}
internal static void Register(Match match)
{
// This method is used to set an expression as the last matcher invoked,
// which is used in the SetupSet to allow matchers in the prop = value
// delegate expression. This delegate is executed in "fluent" mode in
// order to capture the value being set, and construct the corresponding
// methodcall.
// This is also used in the MatcherFactory for each argument expression.
// This method ensures that when we execute the delegate, we
// also track the matcher that was invoked, so that when we create the
// methodcall we build the expression using it, rather than the null/default
// value returned from the actual invocation.
if (MatcherObserver.IsActive(out var observer))
{
observer.OnMatch(match);
}
}
}
/// <summary>
/// Allows creating custom value matchers that can be used on setups and verification,
/// completely replacing the built-in <see cref="It"/> class with your own
/// argument matching rules.
/// </summary>
/// <typeparam name="T">Type of the value to match.</typeparam>
public class Match<T> : Match, IEquatable<Match<T>>
{
internal Predicate<T> Condition { get; set; }
internal Action<T> Success { get; set; }
internal Match(Predicate<T> condition, Expression<Func<T>> renderExpression, Action<T> success = null)
{
this.Condition = condition;
this.RenderExpression = renderExpression.Body.Apply(EvaluateCaptures.Rewriter);
this.Success = success;
}
internal override bool Matches(object argument, Type parameterType)
{
return CanCast(argument) && this.Condition((T)argument);
}
internal override void SetupEvaluatedSuccessfully(object argument, Type parameterType)
{
Debug.Assert(this.Matches(argument, parameterType));
Debug.Assert(CanCast(argument));
this.Success?.Invoke((T)argument);
}
private static bool CanCast(object value)
{
if (value != null)
{
return value is T;
}
else
{
var t = typeof(T);
return !t.IsValueType || (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>));
}
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
return obj is Match<T> other && this.Equals(other);
}
/// <inheritdoc/>
public bool Equals(Match<T> other)
{
if (this.Condition == other.Condition)
{
return true;
}
else if (this.Condition.GetMethodInfo() != other.Condition.GetMethodInfo())
{
return false;
}
else if (!(this.RenderExpression is MethodCallExpression ce && ce.Method.DeclaringType == typeof(Match)))
{
return ExpressionComparer.Default.Equals(this.RenderExpression, other.RenderExpression);
}
else
{
return false; // The test documented in `MatchFixture.Equality_ambiguity` is caused by this.
// Returning true would break equality even worse. The only way to resolve the
// ambiguity is to either add a render expression to your custom matcher, or
// to test both `Condition.Target` objects for structural equality.
}
}
/// <inheritdoc/>
public override int GetHashCode() => 0;
}
internal sealed class MatchFactory : Match
{
private readonly Func<object, Type, bool> condition;
internal MatchFactory(Func<object, Type, bool> condition, LambdaExpression renderExpression)
{
Debug.Assert(condition != null);
Debug.Assert(renderExpression != null);
this.condition = condition;
this.RenderExpression = renderExpression.Body.Apply(EvaluateCaptures.Rewriter);
}
internal override bool Matches(object argument, Type parameterType)
{
var canCast = (Predicate<object>)Delegate.CreateDelegate(typeof(Predicate<object>), canCastMethod.MakeGenericMethod(parameterType));
return canCast(argument) && condition(argument, parameterType);
}
internal override void SetupEvaluatedSuccessfully(object argument, Type parameterType)
{
Debug.Assert(this.Matches(argument, parameterType));
}
private static bool CanCast<T>(object value)
{
if (value != null)
{
return value is T;
}
else
{
var t = typeof(T);
return !t.IsValueType || (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>));
}
}
private static readonly MethodInfo canCastMethod = typeof(MatchFactory).GetMethod("CanCast", BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.DeclaredOnly);
// TODO: Check whether we need to implement `IEquatable<>` to make this work with delegate-based
// setup & verification methods such as `SetupSet`!
}
}