How to forward to another object when using .NET Moq?

╄→尐↘猪︶ㄣ 提交于 2020-01-24 04:17:10

问题


Given an object, I would like to create a mock that implements the interface of the object and mocks one method, but forwards the rest of the methods to the real object, not the base class.

For example:

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 ?

So, in the example I want to mock just ISqlUtil.SpecialMethodToBeMocked and forward the rest of methods/properties to the existing instance sqlUtil.

Is it possible in Moq.NET ?

EDIT 1

It should work for generic methods as well.


回答1:


You can't do this with Moq out of the box. However, I think you can achieve basically what you want if you go down to the next layer and use Castle DynamicProxy directly (which is what's underneath Moq).

So, given the following base code to simulate your issue (essentially, an interface, a concrete implementation and a factory because the concrete is hard to 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;
    }
}

You can then have the following test:

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());
}

The method interceptor looks like this:

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();
        }
    }
}

Essentially, the interceptor checks if the method being called matches the one you're interested in and if so, returns the stored return value. Otherwise, it calls Proceed, which delegates the method call onto the concrete object supplied when the proxy was created.

The example code uses strings rather than lambdas to specify the method to intercept, obviously this could be changed (exercise for the reader). Also, this isn't using Moq, so you lose the Setup, Returns and Verify elements, which are replaced by the Interceptor, so this may be too far away from what you're after to be useful, however depending what your code really looks like it may be a viable alternative approach.




回答2:


If you're unable to mock the class and delegate calls to the base by default, then you'll have to manually wire up the delegation to your separate instance.

var util = GetSqlUtil();

var mockUtil = new Mock<ISqlUtil>(MockBehavior.Strict);
mockUtil.Setup(x => x.SomeCall(...)).Returns<...>(args => util.SomeCall(args));



回答3:


Having been successful with tricking Moq into creating a proxy for given class instance in my other SO answer here, I thought it would be easy to tweak the solution for your case of a given interface implementation.

No way

If you think of, it it makes sense: interface has no implementateion. And since Moq is aware mocked type is an interface - it does not even try to call the underlying proxy. That's it, end of story.

For those who don't give up easily

spoiler: still no luck

Looking at the library source code, I had a theory that it might be possible to force the correct execution path:

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.
......

for that we could fulfill two conditions:

  1. mock.TargetType should be a target class instance type
  2. this.InheritedInterfaces should contain our interface

the second one is easy enough to build:

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);
}

but as far as I'm aware, overriding an expression-bodied property marked internal (which Mock<>.TargetType is) is impossible without Reflection.Emit artillery, where it will likely become infeasible due to amonunt of overriding and subclassing required - you might be better off just forking Moq and patching the source code in this case (or submitting a PR maybe?).

What can be done

It should be possible to generate Setup LINQ expressions that automatically call through to your respective instance implementations:

//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()())
}

But given how much effort it is to account for everything properly, that again is probably not very feasible.



来源:https://stackoverflow.com/questions/35142517/how-to-forward-to-another-object-when-using-net-moq

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!