使用 .NET Moq 时如何转发到另一个对象?

mar*_*ark 6 .net c# moq

给定一个对象,我想创建一个实现对象接口并模拟一个方法的模拟,但将其余方法转发给真实对象,而不是基类

例如:

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 ?
Run Code Online (Sandbox Code Playgroud)

因此,在示例中,我只想模拟ISqlUtil.SpecialMethodToBeMocked并将其余方法/属性转发到现有实例sqlUtil

在 Moq.NET 中可能吗?

编辑 1

它也应该适用于泛型方法。

for*_*rir 6

你不能用开箱即用的 Moq 来做到这一点。但是,我认为如果您深入到下一层并直接使用 Castle DynamicProxy(这是 Moq 下的内容),您基本上可以实现您想要的。

因此,给定以下基本代码来模拟您的问题(本质上,一个接口、一个具体实现和一个工厂,因为具体很难制作/设置):

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,您可以进行以下测试:

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());
}
Run Code Online (Sandbox Code Playgroud)

方法拦截器看起来像这样:

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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

本质上,拦截器检查被调用的方法是否与您感兴趣的方法匹配,如果是,则返回存储的返回值。否则,它调用Proceed,它将方法调用委托给创建代理时提供的具体对象。

示例代码使用字符串而不是 lambda 来指定要拦截的方法,显然这可以改变(读者练习)。此外,这不是使用 Moq,因此您会丢失被拦截器替换的Setup,ReturnsVerify元素,因此这可能与您所追求的内容相距太远而无法使用,但是取决于您的代码的实际情况可能是一种可行的替代方法。