From e78509bd474a4ed6060e0f434709b2d372fdbaa2 Mon Sep 17 00:00:00 2001 From: Dominique Schuppli Date: Sun, 13 Feb 2022 21:52:31 +0100 Subject: [PATCH] Restore LINQ to Mocks' ability to set any property --- src/Moq/Linq/MockSetupsBuilder.cs | 2 +- src/Moq/Linq/Mocks.cs | 22 ------------- src/Moq/Mock.cs | 54 ++++++++++++++++++++++++++++--- 3 files changed, 50 insertions(+), 28 deletions(-) diff --git a/src/Moq/Linq/MockSetupsBuilder.cs b/src/Moq/Linq/MockSetupsBuilder.cs index 55fa63e6e..cac7e3f1b 100644 --- a/src/Moq/Linq/MockSetupsBuilder.cs +++ b/src/Moq/Linq/MockSetupsBuilder.cs @@ -132,7 +132,7 @@ private static Expression ConvertToSetupReturns(Expression left, Expression righ var rewrittenLeft = v.Visit(left); return Expression.Call( - Mocks.SetupReturnsMethod, + Mock.SetupReturnsMethod, // mock: Expression.Call( Mock.GetMethod.MakeGenericMethod(v.MockObject.Type), diff --git a/src/Moq/Linq/Mocks.cs b/src/Moq/Linq/Mocks.cs index cf9e1b462..d7b2e1e6c 100644 --- a/src/Moq/Linq/Mocks.cs +++ b/src/Moq/Linq/Mocks.cs @@ -123,27 +123,5 @@ private static IEnumerable CreateMocks(MockBehavior behavior) where T : cl } while (true); } - - internal static readonly MethodInfo SetupReturnsMethod = - typeof(Mocks).GetMethod(nameof(SetupReturns), BindingFlags.NonPublic | BindingFlags.Static); - - internal static bool SetupReturns(Mock mock, LambdaExpression expression, object value) - { - if (expression.Body is MemberExpression me - && me.Member is PropertyInfo pi - && !(pi.CanRead(out var getter) && getter.CanOverride() && ProxyFactory.Instance.IsMethodVisible(getter, out _)) - && pi.CanWrite(out _)) - { - // LINQ to Mocks allows setting non-interceptable properties, which is handy e.g. when initializing DTOs. - Mock.SetupSet(mock, expression, propertyToSet: pi, value); - } - else - { - var setup = Mock.Setup(mock, expression, condition: null); - setup.SetReturnValueBehavior(value); - } - - return true; - } } } diff --git a/src/Moq/Mock.cs b/src/Moq/Mock.cs index 0a9aa6368..891439b0d 100644 --- a/src/Moq/Mock.cs +++ b/src/Moq/Mock.cs @@ -524,17 +524,61 @@ internal static MethodCall SetupSet(Mock mock, LambdaExpression expression, Cond return Mock.Setup(mock, expression, condition); } - // This specialized version of `SetupSet` exists to let `Mock.Of` support properties that are not overridable. - // Note that we generally prefer having a setup for a property's return value, but in this case, that isn't possible. - internal static void SetupSet(Mock mock, LambdaExpression expression, PropertyInfo propertyToSet, object value) + internal static readonly MethodInfo SetupReturnsMethod = + typeof(Mock).GetMethod(nameof(SetupReturns), BindingFlags.NonPublic | BindingFlags.Static); + + // This specialized setup method is used to set up a single `Mock.Of` predicate. + // Unlike other setup methods, LINQ to Mocks can set non-interceptable properties, which is handy when initializing DTOs. + internal static bool SetupReturns(Mock mock, LambdaExpression expression, object value) { Guard.NotNull(expression, nameof(expression)); - Mock.SetupRecursive(mock, expression, setupLast: (targetMock, _, __) => + Mock.SetupRecursive(mock, expression, setupLast: (targetMock, oe, part) => { - propertyToSet.SetValue(targetMock.Object, value, null); + var originalExpression = (LambdaExpression)oe; + + // There are two special cases involving settable properties where we do something other than creating a new setup: + + if (originalExpression.IsProperty()) + { + var pi = originalExpression.ToPropertyInfo(); + if (pi.CanWrite(out var setter)) + { + if (pi.CanRead(out var getter) && getter.CanOverride() && ProxyFactory.Instance.IsMethodVisible(getter, out _)) + { + if (setter.CanOverride() && ProxyFactory.Instance.IsMethodVisible(setter, out _) + && targetMock.MutableSetups.FindLast(s => s is StubbedPropertiesSetup) is StubbedPropertiesSetup sps) + { + // (a) We have a mock where `SetupAllProperties` was called, and the property can be fully stubbed. + // (A property can be "fully stubbed" if both its accessors can be intercepted.) + // In this case, we set the property's internal backing field directly on the setup. + sps.SetProperty(pi.Name, value); + return null; + } + } + else + { + // (b) The property is settable, but Moq is unable to intercept the getter, + // so setting up the setter would be pointless and the property also cannot be fully stubbed. + // In this case, the best thing we can do is to simply invoke the setter. + pi.SetValue(targetMock.Object, value, null); + return null; + } + } + } + + // For all other cases, we create a regular setup. + + Guard.IsOverridable(part.Method, part.Expression); + Guard.IsVisibleToProxyFactory(part.Method); + + var setup = new MethodCall(originalExpression, targetMock, condition: null, expectation: part); + setup.SetReturnValueBehavior(value); + targetMock.MutableSetups.Add(setup); return null; }, allowNonOverridableLastProperty: true); + + return true; } internal static MethodCall SetupAdd(Mock mock, LambdaExpression expression, Condition condition)