From de41b2b5a26a1acc15cc39443c643d041dea1aeb Mon Sep 17 00:00:00 2001 From: Vladimir Khvostov Date: Tue, 9 May 2023 22:06:12 -0400 Subject: [PATCH] mock.Protected().Setup("Foo") fails base implementation of Foo is hidden in the derived class (#1341) --- CHANGELOG.md | 4 ++ src/Moq/Protected/ProtectedMock.cs | 22 +++++++-- tests/Moq.Tests/ProtectedMockFixture.cs | 66 ++++++++++++++++++++++++- 3 files changed, 88 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 151fcf2e6..649b2b9a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ The format is loosely based on [Keep a Changelog](http://keepachangelog.com/en/1 ## Unreleased +#### Fixed + +* mock.Protected().Setup("Foo") fails if base implementation of the method is hidden in the derived class (#1341) + #### Added * `Mock.RaiseAsync` method for raising "async" events, i.e. events that use a `Func<..., Task>` or `Func<..., ValueTask>` delegate. (@stakx, #1313) diff --git a/src/Moq/Protected/ProtectedMock.cs b/src/Moq/Protected/ProtectedMock.cs index 95eb65b2d..dbc1b794f 100644 --- a/src/Moq/Protected/ProtectedMock.cs +++ b/src/Moq/Protected/ProtectedMock.cs @@ -326,8 +326,24 @@ private static MethodInfo GetMethod(string methodName, Type[] genericTypeArgumen .Select(m => m.MakeGenericMethod(genericTypeArguments)); } - return methods - .SingleOrDefault(m => m.GetParameterTypes().CompareTo(argTypes, exact, considerTypeMatchers: false)); + methods = methods.Where(m => m.GetParameterTypes().CompareTo(argTypes, exact, considerTypeMatchers: false)); + + if (methods.Count() < 2) + { + return methods.FirstOrDefault(); + } + + for (Type type = typeof(T); type != typeof(object); type = type.BaseType) + { + var method = methods.SingleOrDefault(m => m.DeclaringType == typeof(T)); + + if (method != null) + { + return method; + } + } + + return null; } private static Expression> GetMethodCall(MethodInfo method, object[] args) @@ -543,7 +559,7 @@ private static Expression ToExpressionArg(Type type, object arg) } return expression; } - + if (IsItRefAny(expression)) { return expression; diff --git a/tests/Moq.Tests/ProtectedMockFixture.cs b/tests/Moq.Tests/ProtectedMockFixture.cs index 946b13edd..c6f81c888 100644 --- a/tests/Moq.Tests/ProtectedMockFixture.cs +++ b/tests/Moq.Tests/ProtectedMockFixture.cs @@ -475,6 +475,39 @@ public void SetupResultAllowsProtectedMethodInBaseClass() Assert.Equal(5, mock.Object.DoProtectedReturnGeneric()); } + [Fact] + public void SetupResultAllowsHiddenVirtualMethods() + { + var mock = new Mock(); + mock.Protected() + .Setup("ProtectedHiddenInt") + .Returns(5); + + Assert.Equal(5, mock.Object.DoProtectedHiddenInt()); + Assert.Equal(2, ((FooBase)mock.Object).DoProtectedHiddenInt()); + + mock.Protected() + .Setup("ProtectedHiddenWithGenericParam", new[] { typeof(int) }, false) + .Returns(5); + Assert.Equal(5, mock.Object.DoProtectedHiddenWithGenericParam()); + + mock.Protected() + .Setup("ProtectedHiddenSum", genericTypeArguments: Array.Empty(), exactParameterMatch: true, 1, 2) + .Returns(3); + + Assert.Equal(3, mock.Object.DoProtectedHiddenSum(1, 2)); + + mock.Protected() + .Setup("ProtectedHiddenSum", genericTypeArguments: Array.Empty(), exactParameterMatch: true, 1, 2, 3) + .Returns(6); + Assert.Equal(6, mock.Object.DoProtectedHiddenSum(1, 2, 3)); + + mock.Protected() + .Setup("ProtectedHiddenSum", genericTypeArguments: Array.Empty(), exactParameterMatch: true, 1, 2, 3, 4) + .Returns(10); + Assert.Equal(10, mock.Object.DoProtectedHiddenSum(1, 2, 3, 4)); + } + [Fact] public void SetupResultDefaulTwoOverloadsWithDerivedClassThrowsInvalidOperationException() { @@ -1041,7 +1074,7 @@ public void SetMatcherExpressionProperty(MethodCallExpression expression) } protected virtual LambdaExpression LambdaExpressionProperty { get; set; } - + public void SetLambdaExpressionProperty(LambdaExpression expression) { LambdaExpressionProperty = expression; @@ -1111,6 +1144,14 @@ public string DoTwoArgs(string arg, int arg1) return this.TwoArgs(arg, arg1); } + public int DoProtectedHiddenInt() => this.ProtectedHiddenInt(); + + public T DoProtectedHiddenWithGenericParam() => this.ProtectedHiddenWithGenericParam(); + + public int DoProtectedHiddenSum(int op1, int op2) => this.ProtectedHiddenSum(op1, op2); + + public int DoProtectedHiddenSum(int op1, int op2, int op3) => this.ProtectedHiddenSum(op1, op2, op3); + public string GetProtectedValue() { return this.ProtectedValue; @@ -1194,10 +1235,33 @@ protected virtual string TwoArgs(string arg, int arg1) { return arg; } + + protected virtual int ProtectedHiddenInt() => 2; + + protected virtual T ProtectedHiddenWithGenericParam() => default(T); + + protected new virtual int ProtectedHiddenSum(int op1, int op2) => throw new NotImplementedException(); + + protected new virtual int ProtectedHiddenSum(int op1, int op2, int op3) => throw new NotImplementedException(); } public class FooDerived : FooBase { + protected new virtual int ProtectedHiddenInt() => 22; + + protected new virtual T ProtectedHiddenWithGenericParam() => default(T); + + protected new virtual int ProtectedHiddenSum(int op1, int op2) => throw new NotImplementedException(); + + protected virtual int ProtectedHiddenSum(int op1, int op2, int op3, int op4) => throw new NotImplementedException(); + + public new int DoProtectedHiddenInt() => this.ProtectedHiddenInt(); + + public new T DoProtectedHiddenWithGenericParam() => this.ProtectedHiddenWithGenericParam(); + + public new int DoProtectedHiddenSum(int op1, int op2) => this.ProtectedHiddenSum(op1, op2); + + public int DoProtectedHiddenSum(int op1, int op2, int op3, int op4) => this.ProtectedHiddenSum(op1, op2, op3, op4); } public class MyBase { }