Skip to content

Commit

Permalink
Merge pull request #1189 from tonyhallett/fix-protectedmock-expressio…
Browse files Browse the repository at this point in the history
…n-type-args

fix ProtectedMock fails when arguments are of type Expression #1188
  • Loading branch information
stakx committed Aug 3, 2021
2 parents 9b0e1cd + f6a0837 commit 6e2eba7
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 19 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -15,6 +15,7 @@ and `Setup(x => x.GetFooAsync(It.IsAny<string>()).Result).Throws((string s) => n

#### Fixed

* mock.Protected() setup methods fail when argument is of type Expression (@tonyhallett, #1189)
* Parameter is invalid in Protected().SetupSet() ... VerifySet (@tonyhallett, #1186)

* Virtual properties and automocking not working for `mock.Protected().As<>()` (@tonyhallett, #1185)
Expand Down
72 changes: 53 additions & 19 deletions src/Moq/Protected/ProtectedMock.cs
Expand Up @@ -467,26 +467,15 @@ private static Type[] ToArgTypes(object[] args)
{
types[index] = ((MethodCallExpression)expr).Method.ReturnType;
}
else if (ItRefAnyField(expr) is FieldInfo itRefAnyField)
{
types[index] = itRefAnyField.FieldType.MakeByRefType();
}
else if (expr.NodeType == ExpressionType.MemberAccess)
{
var member = (MemberExpression)expr;
if (member.Member is FieldInfo field)
{
// Test for special case: `It.Ref<TValue>.IsAny`
if (field.Name == nameof(It.Ref<object>.IsAny))
{
var fieldDeclaringType = field.DeclaringType;
if (fieldDeclaringType.IsGenericType)
{
var fieldDeclaringTypeDefinition = fieldDeclaringType.GetGenericTypeDefinition();
if (fieldDeclaringTypeDefinition == typeof(It.Ref<>))
{
types[index] = field.FieldType.MakeByRefType();
continue;
}
}
}

types[index] = field.FieldType;
}
else if (member.Member is PropertyInfo property)
Expand All @@ -510,16 +499,61 @@ private static Type[] ToArgTypes(object[] args)
return types;
}

private static Expression ToExpressionArg(Type type, object arg)
private static bool IsItRefAny(Expression expression)
{
if (arg is LambdaExpression lambda)
return ItRefAnyField(expression) != null;
}

private static FieldInfo ItRefAnyField(Expression expr)
{
FieldInfo itRefAnyField = null;

if (expr.NodeType == ExpressionType.MemberAccess)
{
return lambda.Body;
var member = (MemberExpression)expr;
if (member.Member is FieldInfo field)
{
if (field.Name == nameof(It.Ref<object>.IsAny))
{
var fieldDeclaringType = field.DeclaringType;
if (fieldDeclaringType.IsGenericType)
{
var fieldDeclaringTypeDefinition = fieldDeclaringType.GetGenericTypeDefinition();
if (fieldDeclaringTypeDefinition == typeof(It.Ref<>))
{
itRefAnyField = field;
}
}
}
}
}

return itRefAnyField;
}

private static Expression ToExpressionArg(Type type, object arg)
{
if (arg is Expression expression)
{
return expression;
if (!type.IsAssignableFrom(expression.GetType()))
{
if(arg is LambdaExpression lambda)
{
expression = lambda.Body;
}
return expression;
}

if (IsItRefAny(expression))
{
return expression;
}

if (expression.IsMatch(out _))
{
return expression;
}

}

return Expression.Constant(arg, type);
Expand Down
95 changes: 95 additions & 0 deletions tests/Moq.Tests/ProtectedMockFixture.cs
Expand Up @@ -2,6 +2,7 @@
// All rights reserved. Licensed under the BSD 3-Clause License; see License.txt.

using System;
using System.Linq.Expressions;

using Moq.Protected;

Expand Down Expand Up @@ -855,6 +856,70 @@ public void DoesNotThrowIfVerifySetPropertyTimesReached()
mock.Protected().VerifySet<string>("ProtectedValue", Times.Exactly(2), ItExpr.IsAny<string>());
}

[Fact]
public void SetupShouldWorkWithExpressionTypes()
{
var mock = new Mock<FooBase>();
var mocked = mock.Object;
var protectedMock = mock.Protected();

var expression = Expression.Constant(1);
Expression setExpression1 = null;
protectedMock.SetupSet<Expression>("ExpressionProperty", expression).Callback(expr => setExpression1 = expr);
mocked.SetExpressionProperty(expression);
Assert.Same(expression, setExpression1);

var expression2 = Expression.Constant(2);
Expression setExpression2 = null;
protectedMock.SetupSet<Expression>("ExpressionProperty", ItExpr.Is<ConstantExpression>(e => (int)e.Value == 2)).Callback(expr => setExpression2 = expr);
mocked.SetExpressionProperty(expression2);
Assert.Same(expression2, setExpression2);

var constantExpression = Expression.Constant(1);
ConstantExpression setConstantExpression = null;
protectedMock.SetupSet<ConstantExpression>("NotAMatcherExpressionProperty", constantExpression).Callback(expr => setConstantExpression = expr);
mocked.SetNotAMatcherExpressionProperty(constantExpression);
Assert.Same(constantExpression, setConstantExpression);

var constantExpression2 = Expression.Constant(2);
ConstantExpression setConstantExpression2 = null;
protectedMock.SetupSet<ConstantExpression>("NotAMatcherExpressionProperty", ItExpr.Is<ConstantExpression>(e => (int)e.Value == 2)).Callback(expr => setConstantExpression2 = expr);
mocked.SetNotAMatcherExpressionProperty(constantExpression2);
Assert.Same(constantExpression2, setConstantExpression2);

var method = typeof(FooBase).GetMethod(nameof(FooBase.MethodForReflection));
var methodCallExpression = Expression.Call(method);
MethodCallExpression setMethodCallExpression = null;
protectedMock.SetupSet<MethodCallExpression>("MatcherExpressionProperty", methodCallExpression).Callback(expr => setMethodCallExpression = expr);
mocked.SetMatcherExpressionProperty(methodCallExpression);
Assert.Same(methodCallExpression, setMethodCallExpression);

var methodCallExpression2 = Expression.Call(typeof(FooBase).GetMethod(nameof(FooBase.MethodForReflection2)));
MethodCallExpression setMethodCallExpression2 = null;
protectedMock.SetupSet<MethodCallExpression>("MatcherExpressionProperty", ItExpr.Is<MethodCallExpression>(e => e.Method != method)).Callback(expr => setMethodCallExpression2 = expr);
mocked.SetMatcherExpressionProperty(methodCallExpression2);
Assert.Same(methodCallExpression2, setMethodCallExpression2);

Expression<Func<int, bool>> lambdaExpression = i => i < 5;
LambdaExpression setLambdaExpression = null;
protectedMock.SetupSet<LambdaExpression>("LambdaExpressionProperty", lambdaExpression).Callback(expr => setLambdaExpression = expr);
mocked.SetLambdaExpressionProperty(lambdaExpression);
Assert.Same(lambdaExpression, setLambdaExpression);

Expression<Func<int, int>> lambdaExpression2 = i => i;
LambdaExpression setLambdaExpression2 = null;
protectedMock.SetupSet<LambdaExpression>("LambdaExpressionProperty", ItExpr.Is<LambdaExpression>(e => e == lambdaExpression2)).Callback(expr => setLambdaExpression2 = expr);
mocked.SetLambdaExpressionProperty(lambdaExpression2);
Assert.Same(lambdaExpression2, setLambdaExpression2);

}

public class ExpectedException : Exception
{
private static ExpectedException instance = new ExpectedException();
public static ExpectedException Instance => instance;
}

public class MethodOverloads
{
public void ExecuteDo(int a, int b)
Expand Down Expand Up @@ -952,6 +1017,36 @@ protected virtual FooBase Overloaded(MyDerived myBase)

public class FooBase
{
public static void MethodForReflection() { }
public static void MethodForReflection2() { }
protected virtual Expression ExpressionProperty { get; set; }

public void SetExpressionProperty(Expression expression)
{
ExpressionProperty = expression;
}

protected virtual ConstantExpression NotAMatcherExpressionProperty { get; set; }

public void SetNotAMatcherExpressionProperty(ConstantExpression expression)
{
NotAMatcherExpressionProperty = expression;
}

protected virtual MethodCallExpression MatcherExpressionProperty { get; set; }

public void SetMatcherExpressionProperty(MethodCallExpression expression)
{
MatcherExpressionProperty = expression;
}

protected virtual LambdaExpression LambdaExpressionProperty { get; set; }

public void SetLambdaExpressionProperty(LambdaExpression expression)
{
LambdaExpressionProperty = expression;
}

public virtual string PublicValue { get; set; }

protected internal virtual string ProtectedInternalValue { get; set; }
Expand Down

0 comments on commit 6e2eba7

Please sign in to comment.