使用.NET Moq 时如何转发到另一个对象?
How to forward to another object when using .NET Moq?
给定一个对象,我想创建一个实现对象接口并模拟一个方法的模拟,但将其余方法转发给真实对象,不是基础class.
例如:
ISqlUtil sqlUtil = GetTheRealSqlUtilObjectSomehow(...);
var mock = new Mock<ISqlUtil>();
mock.Setup(o => o.SpecialMethodToBeMocked(...)).Returns<...>(...)
// Here I would like to delegate the rest of the methods to the real sqlUtil object. How ?
因此,在示例中,我只想模拟 ISqlUtil.SpecialMethodToBeMocked
并将 methods/properties 的其余部分转发给现有实例 sqlUtil
。
Moq.NET可以吗?
编辑 1
它也应该适用于泛型方法。
如果默认情况下您无法模拟 class 和委托对基础的调用,则必须手动将委托连接到您的单独实例。
var util = GetSqlUtil();
var mockUtil = new Mock<ISqlUtil>(MockBehavior.Strict);
mockUtil.Setup(x => x.SomeCall(...)).Returns<...>(args => util.SomeCall(args));
开箱即用的最小起订量无法做到这一点。但是,我认为如果您进入下一层并直接使用 Castle DynamicProxy(这是 Moq 下的内容),您基本上可以实现您想要的。
因此,给出以下基本代码来模拟您的问题(本质上是一个接口、一个具体实现和一个工厂,因为具体很难make/setup):
public interface ISqlUtil {
T SomeGenericMethod<T>(T args);
int SomeMethodToIntercept();
}
public class ConcreteSqlUtil : ISqlUtil {
public T SomeGenericMethod<T>(T args){
return args;
}
public int SomeMethodToIntercept() {
return 42;
}
}
public class SqlUtilFactory {
public static ISqlUtil CreateSqlUtil() {
var rVal = new ConcreteSqlUtil();
// Some Complex setup
return rVal;
}
}
您可以进行以下测试:
public void TestCanInterceptMethods() {
// Create a concrete instance, using the factory
var coreInstance = SqlUtilFactory.CreateSqlUtil();
// Test that the concrete instance works
Assert.AreEqual(42, coreInstance.SomeMethodToIntercept());
Assert.AreEqual(40, coreInstance.SomeGenericMethod(40));
// Create a proxy generator (you'll probably want to put this
// somewhere static so that it's caching works if you use it)
var generator = new Castle.DynamicProxy.ProxyGenerator();
// Use the proxy to generate a new class that implements ISqlUtil
// Note the concrete instance is passed into the construction
// As is an instance of MethodInterceptor (see below)
var proxy = generator.CreateInterfaceProxyWithTarget<ISqlUtil>(coreInstance,
new MethodInterceptor<int>("SomeMethodToIntercept", 33));
// Check that calling via the proxy still delegates to existing
// generic method
Assert.AreEqual(45, proxy.SomeGenericMethod(45));
// Check that calling via the proxy returns the result we've specified
// for our intercepted method
Assert.AreEqual(33, proxy.SomeMethodToIntercept());
}
方法拦截器如下所示:
public class MethodInterceptor<T> : Castle.DynamicProxy.IInterceptor {
private T _returns;
private string _methodName;
public MethodInterceptor(string methodName, T returns) {
_returns = returns;
_methodName = methodName;
}
public void Intercept(IInvocation invocation) {
if (invocation.Method.Name == _methodName) {
invocation.ReturnValue = _returns;
}
else {
invocation.Proceed();
}
}
}
本质上,拦截器检查被调用的方法是否与您感兴趣的方法相匹配,如果是,return存储的 return 值。否则,它调用 Proceed
,它将方法调用委托给创建代理时提供的具体对象。
示例代码使用字符串而不是 lambda 来指定拦截方法,显然这可以更改(reader 的练习)。此外,这没有使用 Moq,因此您丢失了 Setup
、Returns
和 Verify
元素,这些元素已被拦截器取代,因此这可能与您的内容相去甚远re 之后会有用,但是取决于您的代码的实际情况,它可能是一种可行的替代方法。
成功诱使 Moq 为给定 class 实例 创建代理,我认为调整解决方案很容易您的给定 接口 实现案例。
不行
如果你想到,这是有道理的:接口没有实现。而且由于 Moq 知道模拟类型是一个接口 - 它 does not even try to call the underlying proxy。到此为止,故事结束。
献给不轻易放弃的人
剧透:仍然没有运气
看着library source code,我有一个理论,它可能会强制正确的执行路径:
if (mock.TargetType.IsInterface) // !!! needs to be true here
{
// !!! we end up here and proceed to `DefaultValueProvider`
}
else
{
Debug.Assert(mock.TargetType.IsClass); // !!! needs to pass here
Debug.Assert(mock.ImplementsInterface(declaringType)); // !!! needs to pass here
// Case 2: Explicitly implemented interface method of a class proxy.
......
为此我们可以满足两个条件:
mock.TargetType
应该是目标 class 实例类型
this.InheritedInterfaces
应该包含我们的接口
第二个很容易搭建:
private void AddInheritedInterfaces(T targetInstance)
{
var moqAssembly = Assembly.Load(nameof(Moq));
var mockType = moqAssembly.GetType("Moq.Mock`1");
var concreteType = mockType.MakeGenericType(typeof(T));
var fi = concreteType.GetField("inheritedInterfaces", BindingFlags.NonPublic | BindingFlags.Static);
var t = targetInstance.GetType()
.GetInterfaces()
.ToArray();
fi.SetValue(null, t);
}
但据我所知,如果没有 Reflection.Emit
大炮,则无法覆盖标记为 internal
(Mock<>.TargetType
的表达式)属性,其中由于需要大量的覆盖和子 classing,它可能变得不可行 - 在这种情况下,您最好只分叉 Moq
并修补源代码(或者可能提交 PR?)。
可以做什么
应该可以生成 Setup
LINQ 表达式,自动调用您各自的实例实现:
//something along these lines, but this is basically sudocode
ISqlUtil sqlUtil = GetTheRealSqlUtilObjectSomehow(...);
var mock = new Mock<ISqlUtil>();
foreach(var methodInfo in typeof(ISqlUtil).GetMembers())
{ mock.Setup(Expression.Member(methodInfo)).Returns(Expression.Lambda(Expression.Call(methodInfo)).Compile()())
}
但是考虑到正确解释所有事情需要付出多少努力,这可能也不太可行。
给定一个对象,我想创建一个实现对象接口并模拟一个方法的模拟,但将其余方法转发给真实对象,不是基础class.
例如:
ISqlUtil sqlUtil = GetTheRealSqlUtilObjectSomehow(...);
var mock = new Mock<ISqlUtil>();
mock.Setup(o => o.SpecialMethodToBeMocked(...)).Returns<...>(...)
// Here I would like to delegate the rest of the methods to the real sqlUtil object. How ?
因此,在示例中,我只想模拟 ISqlUtil.SpecialMethodToBeMocked
并将 methods/properties 的其余部分转发给现有实例 sqlUtil
。
Moq.NET可以吗?
编辑 1
它也应该适用于泛型方法。
如果默认情况下您无法模拟 class 和委托对基础的调用,则必须手动将委托连接到您的单独实例。
var util = GetSqlUtil();
var mockUtil = new Mock<ISqlUtil>(MockBehavior.Strict);
mockUtil.Setup(x => x.SomeCall(...)).Returns<...>(args => util.SomeCall(args));
开箱即用的最小起订量无法做到这一点。但是,我认为如果您进入下一层并直接使用 Castle DynamicProxy(这是 Moq 下的内容),您基本上可以实现您想要的。
因此,给出以下基本代码来模拟您的问题(本质上是一个接口、一个具体实现和一个工厂,因为具体很难make/setup):
public interface ISqlUtil {
T SomeGenericMethod<T>(T args);
int SomeMethodToIntercept();
}
public class ConcreteSqlUtil : ISqlUtil {
public T SomeGenericMethod<T>(T args){
return args;
}
public int SomeMethodToIntercept() {
return 42;
}
}
public class SqlUtilFactory {
public static ISqlUtil CreateSqlUtil() {
var rVal = new ConcreteSqlUtil();
// Some Complex setup
return rVal;
}
}
您可以进行以下测试:
public void TestCanInterceptMethods() {
// Create a concrete instance, using the factory
var coreInstance = SqlUtilFactory.CreateSqlUtil();
// Test that the concrete instance works
Assert.AreEqual(42, coreInstance.SomeMethodToIntercept());
Assert.AreEqual(40, coreInstance.SomeGenericMethod(40));
// Create a proxy generator (you'll probably want to put this
// somewhere static so that it's caching works if you use it)
var generator = new Castle.DynamicProxy.ProxyGenerator();
// Use the proxy to generate a new class that implements ISqlUtil
// Note the concrete instance is passed into the construction
// As is an instance of MethodInterceptor (see below)
var proxy = generator.CreateInterfaceProxyWithTarget<ISqlUtil>(coreInstance,
new MethodInterceptor<int>("SomeMethodToIntercept", 33));
// Check that calling via the proxy still delegates to existing
// generic method
Assert.AreEqual(45, proxy.SomeGenericMethod(45));
// Check that calling via the proxy returns the result we've specified
// for our intercepted method
Assert.AreEqual(33, proxy.SomeMethodToIntercept());
}
方法拦截器如下所示:
public class MethodInterceptor<T> : Castle.DynamicProxy.IInterceptor {
private T _returns;
private string _methodName;
public MethodInterceptor(string methodName, T returns) {
_returns = returns;
_methodName = methodName;
}
public void Intercept(IInvocation invocation) {
if (invocation.Method.Name == _methodName) {
invocation.ReturnValue = _returns;
}
else {
invocation.Proceed();
}
}
}
本质上,拦截器检查被调用的方法是否与您感兴趣的方法相匹配,如果是,return存储的 return 值。否则,它调用 Proceed
,它将方法调用委托给创建代理时提供的具体对象。
示例代码使用字符串而不是 lambda 来指定拦截方法,显然这可以更改(reader 的练习)。此外,这没有使用 Moq,因此您丢失了 Setup
、Returns
和 Verify
元素,这些元素已被拦截器取代,因此这可能与您的内容相去甚远re 之后会有用,但是取决于您的代码的实际情况,它可能是一种可行的替代方法。
成功诱使 Moq 为给定 class 实例
不行
如果你想到,这是有道理的:接口没有实现。而且由于 Moq 知道模拟类型是一个接口 - 它 does not even try to call the underlying proxy。到此为止,故事结束。
献给不轻易放弃的人
剧透:仍然没有运气
看着library source code,我有一个理论,它可能会强制正确的执行路径:
if (mock.TargetType.IsInterface) // !!! needs to be true here
{
// !!! we end up here and proceed to `DefaultValueProvider`
}
else
{
Debug.Assert(mock.TargetType.IsClass); // !!! needs to pass here
Debug.Assert(mock.ImplementsInterface(declaringType)); // !!! needs to pass here
// Case 2: Explicitly implemented interface method of a class proxy.
......
为此我们可以满足两个条件:
mock.TargetType
应该是目标 class 实例类型this.InheritedInterfaces
应该包含我们的接口
第二个很容易搭建:
private void AddInheritedInterfaces(T targetInstance)
{
var moqAssembly = Assembly.Load(nameof(Moq));
var mockType = moqAssembly.GetType("Moq.Mock`1");
var concreteType = mockType.MakeGenericType(typeof(T));
var fi = concreteType.GetField("inheritedInterfaces", BindingFlags.NonPublic | BindingFlags.Static);
var t = targetInstance.GetType()
.GetInterfaces()
.ToArray();
fi.SetValue(null, t);
}
但据我所知,如果没有 Reflection.Emit
大炮,则无法覆盖标记为 internal
(Mock<>.TargetType
的表达式)属性,其中由于需要大量的覆盖和子 classing,它可能变得不可行 - 在这种情况下,您最好只分叉 Moq
并修补源代码(或者可能提交 PR?)。
可以做什么
应该可以生成 Setup
LINQ 表达式,自动调用您各自的实例实现:
//something along these lines, but this is basically sudocode
ISqlUtil sqlUtil = GetTheRealSqlUtilObjectSomehow(...);
var mock = new Mock<ISqlUtil>();
foreach(var methodInfo in typeof(ISqlUtil).GetMembers())
{ mock.Setup(Expression.Member(methodInfo)).Returns(Expression.Lambda(Expression.Call(methodInfo)).Compile()())
}
但是考虑到正确解释所有事情需要付出多少努力,这可能也不太可行。