diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9c6b3a700..f7463b6bd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,12 +19,16 @@ The format is loosely based on [Keep a Changelog](http://keepachangelog.com/en/1
* Moq will throw when it detects that an argument matcher will never match anything due to the presence of an implicit conversion operator. (@michelcedric, #897, #898)
+* New algorithm for matching invoked methods against methods specified in setup/verification expressions. (@stakx, #904)
+
#### Added
* Added support for setup and verification of the event handlers through `Setup[Add|Remove]` and `Verify[Add|Remove|All]` (@lepijohnny, #825)
#### Fixed
+* Moq does not mock explicit interface implementation and `protected virtual` correctly. (@oddbear, #657)
+
* `Invocations.Clear()` does not cause `Verify` to fail (@jchessir, #733)
* Regression: `SetupAllProperties` can no longer set up properties whose names start with `Item`. (@mattzink, #870; @kaan-kaya, #869)
@@ -35,6 +39,8 @@ The format is loosely based on [Keep a Changelog](http://keepachangelog.com/en/1
* Regression in 4.12.0: `SetupAllProperties` removes indexer setups. (@stakx, #901)
+* Parameter types are ignored when matching an invoked generic method against setups. (@stakx, #903)
+
## 4.12.0 (2019-06-20)
diff --git a/src/Moq/Extensions.cs b/src/Moq/Extensions.cs
index 96d15ed78..adcedb010 100644
--- a/src/Moq/Extensions.cs
+++ b/src/Moq/Extensions.cs
@@ -26,6 +26,44 @@ public static object GetDefaultValue(this Type type)
return type.IsValueType ? Activator.CreateInstance(type) : null;
}
+ ///
+ /// Gets the least-derived in the given type that provides
+ /// the implementation for the given .
+ ///
+ public static MethodInfo GetImplementingMethod(this MethodInfo method, Type proxyType)
+ {
+ Debug.Assert(method != null);
+ Debug.Assert(proxyType != null);
+ Debug.Assert(proxyType.IsClass);
+
+ if (method.IsGenericMethod)
+ {
+ method = method.GetGenericMethodDefinition();
+ }
+
+ var declaringType = method.DeclaringType;
+
+ if (declaringType.IsInterface)
+ {
+ Debug.Assert(declaringType.IsAssignableFrom(proxyType));
+
+ var map = proxyType.GetInterfaceMap(method.DeclaringType);
+ var index = Array.IndexOf(map.InterfaceMethods, method);
+ Debug.Assert(index >= 0);
+ return map.TargetMethods[index].GetBaseDefinition();
+ }
+ else if (declaringType.IsDelegateType())
+ {
+ return proxyType.GetMethod("Invoke");
+ }
+ else
+ {
+ Debug.Assert(declaringType.IsAssignableFrom(proxyType));
+
+ return method.GetBaseDefinition();
+ }
+ }
+
public static object InvokePreserveStack(this Delegate del, params object[] args)
{
try
diff --git a/src/Moq/Invocation.cs b/src/Moq/Invocation.cs
index 406888d85..6d7c6b5fa 100644
--- a/src/Moq/Invocation.cs
+++ b/src/Moq/Invocation.cs
@@ -1,6 +1,7 @@
// Copyright (c) 2007, Clarius Consulting, Manas Technology Solutions, InSTEDD.
// All rights reserved. Licensed under the BSD 3-Clause License; see License.txt.
+using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
@@ -12,21 +13,26 @@ internal abstract class Invocation : IInvocation
{
private object[] arguments;
private MethodInfo method;
+ private MethodInfo methodImplementation;
+ private readonly Type proxyType;
private object returnValue;
private VerificationState verificationState;
///
/// Initializes a new instance of the class.
///
+ /// The of the concrete proxy object on which a method is being invoked.
/// The method being invoked.
/// The arguments with which the specified is being invoked.
- protected Invocation(MethodInfo method, params object[] arguments)
+ protected Invocation(Type proxyType, MethodInfo method, params object[] arguments)
{
+ Debug.Assert(proxyType != null);
Debug.Assert(arguments != null);
Debug.Assert(method != null);
this.arguments = arguments;
this.method = method;
+ this.proxyType = proxyType;
}
///
@@ -34,6 +40,19 @@ protected Invocation(MethodInfo method, params object[] arguments)
///
public MethodInfo Method => this.method;
+ public MethodInfo MethodImplementation
+ {
+ get
+ {
+ if (this.methodImplementation == null)
+ {
+ this.methodImplementation = this.method.GetImplementingMethod(this.proxyType);
+ }
+
+ return this.methodImplementation;
+ }
+ }
+
///
/// Gets the arguments of the invocation.
///
@@ -45,6 +64,8 @@ protected Invocation(MethodInfo method, params object[] arguments)
IReadOnlyList