Skip to content

Commit 22cce9c

Browse files
authored
Merge duplicate FluentMockVisitor classes (#698)
1 parent 4eab6b7 commit 22cce9c

File tree

5 files changed

+211
-344
lines changed

5 files changed

+211
-344
lines changed

src/Moq/FluentMockVisitor.cs

+194
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
// Copyright (c) 2007, Clarius Consulting, Manas Technology Solutions, InSTEDD.
2+
// All rights reserved. Licensed under the BSD 3-Clause License; see License.txt.
3+
4+
using System;
5+
using System.Diagnostics;
6+
using System.Linq;
7+
using System.Linq.Expressions;
8+
using System.Reflection;
9+
using System.Runtime.CompilerServices;
10+
11+
using Moq.Properties;
12+
13+
namespace Moq
14+
{
15+
/// <summary>
16+
/// This visitor translates multi-dot expressions such as `r.A.B.C` to an executable expression
17+
/// `FluentMock(FluentMock(FluentMock(root, m => m.A), a => a.B), b => b.C)` that, when executed,
18+
/// will cause all "inner mocks" towards the rightmost sub-expression to be automatically mocked.
19+
/// The original expression can be added to the final, rightmost inner mock as a setup.
20+
/// </summary>
21+
internal sealed class FluentMockVisitor : ExpressionVisitor
22+
{
23+
internal static readonly MethodInfo FluentMockMethod =
24+
typeof(FluentMockVisitor).GetMethod(nameof(FluentMock), BindingFlags.NonPublic | BindingFlags.Static);
25+
26+
private bool isAtRightmost;
27+
private readonly Func<ParameterExpression, Expression> resolveRoot;
28+
private readonly bool setupRightmost;
29+
30+
public FluentMockVisitor(Func<ParameterExpression, Expression> resolveRoot, bool setupRightmost)
31+
{
32+
Debug.Assert(resolveRoot != null);
33+
34+
this.isAtRightmost = true;
35+
this.resolveRoot = resolveRoot;
36+
this.setupRightmost = setupRightmost;
37+
}
38+
39+
protected override Expression VisitMember(MemberExpression node)
40+
{
41+
Debug.Assert(node != null);
42+
43+
// Translate differently member accesses over transparent
44+
// compiler-generated types as they are typically the
45+
// anonymous types generated to build up the query expressions.
46+
if (node.Expression.NodeType == ExpressionType.Parameter &&
47+
node.Expression.Type.GetTypeInfo().IsDefined(typeof(CompilerGeneratedAttribute), false))
48+
{
49+
var memberType = node.Member is FieldInfo ?
50+
((FieldInfo)node.Member).FieldType :
51+
((PropertyInfo)node.Member).PropertyType;
52+
53+
// Generate a Mock.Get over the entire member access rather.
54+
// <anonymous_type>.foo => Mock.Get(<anonymous_type>.foo)
55+
return Expression.Call(null,
56+
Mock.GetMethod.MakeGenericMethod(memberType), node);
57+
}
58+
59+
// If member is not mock-able, actually, including being a sealed class, etc.?
60+
if (node.Member is FieldInfo)
61+
throw new NotSupportedException();
62+
63+
var lambdaParam = Expression.Parameter(node.Expression.Type, "mock");
64+
Expression lambdaBody = Expression.MakeMemberAccess(lambdaParam, node.Member);
65+
var targetMethod = GetTargetMethod(node.Expression.Type, ((PropertyInfo)node.Member).PropertyType);
66+
if (isAtRightmost)
67+
{
68+
isAtRightmost = false;
69+
}
70+
71+
return TranslateFluent(node.Expression.Type, ((PropertyInfo)node.Member).PropertyType, targetMethod, Visit(node.Expression), lambdaParam, lambdaBody);
72+
}
73+
74+
protected override Expression VisitMethodCall(MethodCallExpression node)
75+
{
76+
Debug.Assert(node != null);
77+
78+
var lambdaParam = Expression.Parameter(node.Object.Type, "mock");
79+
var lambdaBody = Expression.Call(lambdaParam, node.Method, node.Arguments);
80+
var targetMethod = GetTargetMethod(node.Object.Type, node.Method.ReturnType);
81+
if (isAtRightmost)
82+
{
83+
isAtRightmost = false;
84+
}
85+
86+
return TranslateFluent(node.Object.Type, node.Method.ReturnType, targetMethod, this.Visit(node.Object), lambdaParam, lambdaBody);
87+
}
88+
89+
protected override Expression VisitParameter(ParameterExpression node)
90+
{
91+
Debug.Assert(node != null);
92+
93+
return this.resolveRoot(node);
94+
}
95+
96+
private MethodInfo GetTargetMethod(Type objectType, Type returnType)
97+
{
98+
// dte.Solution =>
99+
if (this.setupRightmost && this.isAtRightmost)
100+
{
101+
//.Setup(mock => mock.Solution)
102+
return typeof(Mock<>)
103+
.MakeGenericType(objectType)
104+
.GetMethods("Setup")
105+
.First(m => m.IsGenericMethod)
106+
.MakeGenericMethod(returnType);
107+
}
108+
else
109+
{
110+
//.FluentMock(mock => mock.Solution)
111+
Guard.Mockable(returnType);
112+
return FluentMockMethod.MakeGenericMethod(objectType, returnType);
113+
}
114+
}
115+
116+
// Args like: string IFoo (mock => mock.Value)
117+
private Expression TranslateFluent(Type objectType,
118+
Type returnType,
119+
MethodInfo targetMethod,
120+
Expression instance,
121+
ParameterExpression lambdaParam,
122+
Expression lambdaBody)
123+
{
124+
var funcType = typeof(Func<,>).MakeGenericType(objectType, returnType);
125+
126+
if (targetMethod.IsStatic)
127+
{
128+
// This is the fluent extension method one, so pass the instance as one more arg.
129+
return Expression.Call(targetMethod, instance, Expression.Lambda(funcType, lambdaBody, lambdaParam));
130+
}
131+
else
132+
{
133+
return Expression.Call(instance, targetMethod, Expression.Lambda(funcType, lambdaBody, lambdaParam));
134+
}
135+
}
136+
137+
/// <summary>
138+
/// Retrieves a fluent mock from the given setup expression.
139+
/// </summary>
140+
private static Mock<TResult> FluentMock<T, TResult>(Mock<T> mock, Expression<Func<T, TResult>> setup)
141+
where T : class
142+
where TResult : class
143+
{
144+
Guard.NotNull(mock, nameof(mock));
145+
Guard.NotNull(setup, nameof(setup));
146+
Guard.Mockable(typeof(TResult));
147+
148+
MethodInfo info;
149+
if (setup.Body.NodeType == ExpressionType.MemberAccess)
150+
{
151+
var memberExpr = ((MemberExpression)setup.Body);
152+
memberExpr.ThrowIfNotMockeable();
153+
154+
info = ((PropertyInfo)memberExpr.Member).GetGetMethod();
155+
}
156+
else if (setup.Body.NodeType == ExpressionType.Call)
157+
{
158+
info = ((MethodCallExpression)setup.Body).Method;
159+
}
160+
else
161+
{
162+
throw new NotSupportedException(string.Format(Resources.UnsupportedExpression, setup.ToStringFixed()));
163+
}
164+
165+
Guard.Mockable(info.ReturnType);
166+
167+
Mock fluentMock;
168+
MockWithWrappedMockObject innerMock;
169+
if (mock.InnerMocks.TryGetValue(info, out innerMock))
170+
{
171+
fluentMock = innerMock.Mock;
172+
}
173+
else
174+
{
175+
fluentMock = ((IMocked)mock.GetDefaultValue(info, useAlternateProvider: DefaultValueProvider.Mock)).Mock;
176+
Mock.SetupAllProperties(fluentMock);
177+
178+
innerMock = new MockWithWrappedMockObject(fluentMock, fluentMock.Object);
179+
// ^^^^^^^^^^^^^^^^^
180+
// NOTE: Above, we are assuming that a default value was returned that is neither a `Task<T>` nor a `ValueTask<T>`,
181+
// i.e. nothing we'd need to first "unwrap" to get at the actual mocked object. This assumption would seem permissible
182+
// since the present method gets called only for multi-dot expressions ("recursive mocking"), which do not allow
183+
// `await` expressions. Therefore we don't need to deal with `Task<T>` nor `ValueTask<T>`, and we proceed as if the
184+
// returned default value were already "unwrapped".
185+
}
186+
187+
var result = (TResult)innerMock.WrappedMockObject;
188+
189+
mock.Setup(setup).Returns(result);
190+
191+
return (Mock<TResult>)fluentMock;
192+
}
193+
}
194+
}

src/Moq/Linq/FluentMockVisitor.cs

-162
This file was deleted.

src/Moq/Linq/MockSetupsBuilder.cs

+9-2
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ private static Expression ConvertToSetupProperty(Expression targetObject, Expres
207207
return ConvertToSetup(targetObject, left, right);
208208

209209
// This will get up to and including the Mock.Get(foo).Setup(mock => mock.Name) call.
210-
var propertySetup = FluentMockVisitor.Accept(left);
210+
var propertySetup = VisitFluent(left);
211211
// We need to go back one level, to the target expression of the Setup call,
212212
// which would be the Mock.Get(foo), where we will actually invoke SetupProperty instead.
213213
if (propertySetup.NodeType != ExpressionType.Call)
@@ -246,8 +246,15 @@ private static Expression ConvertToSetup(Expression targetObject, Expression lef
246246
}
247247

248248
return Expression.NotEqual(
249-
Expression.Call(FluentMockVisitor.Accept(left), returnsMethod, right),
249+
Expression.Call(VisitFluent(left), returnsMethod, right),
250250
Expression.Constant(null));
251251
}
252+
253+
private static Expression VisitFluent(Expression expression)
254+
{
255+
return new FluentMockVisitor(resolveRoot: p => Expression.Call(null, Mock.GetMethod.MakeGenericMethod(p.Type), p),
256+
setupRightmost: true)
257+
.Visit(expression);
258+
}
252259
}
253260
}

0 commit comments

Comments
 (0)