带有类代理尝试的 Moq 完全通用设置会抛出“无法执行后期绑定操作”

问题描述 投票:0回答:1

需要解决的问题

我正在尝试使用 Moq 库创建一个接口模拟

ISomeService
,它将包装它的真实实现
SomeService

现在我正在手动执行此操作,如下所示:

var instance = new SomeService();
var mock = new Mock<ISomeSorvice>();
mock.Setup(x => x.Run(It.IsAny<int>()))
    .Returns(x => instance.Run(x));

它调用真正的实现,因此我的测试更加可靠,并且我得到了验证等模拟的好处。问题是我必须为我使用的每个方法和每个服务实现它,这不是可行的方法。我的目标是完全自动化的解决方案。

我做了什么

我的

Setup()
部分是

// (x)
var mockParameter = Expression.Parameter(typeof(T), "x");
  
// It.IsAny<T>()
var isAnyMethod = typeof(It).GetMethod(nameof(It.IsAny));
var isAny = parameters.Select(x => Expression.Call(isAnyMethod.MakeGenericMethod(x.ParameterType))).ToArray();

// x.Run(It.IsAny<T>())
mockCall = Expression.Call(mockParameter, method, isAny);

现在是有趣的部分。如果我这样定义 lambda:

// (x) => x.Run(It.IsAny<T>())
var mockLambda = Expression.Lambda(mockCall, mockParameter);

它产生一个

LambdaExpression
并且它不能与需要完全定义表达式的
Setup
方法定义一起使用:

public ISetup<T, TResult> Setup<TResult>(Expression<Func<T, TResult>> expression)

我不能直接调用

Setup
,因为我显然不知道
TResult
的类型,因此我求助于反射:

var setupMethod = typeof(Mock<>)
    .GetMethods()
    .Where(x => x.Name == nameof(mock.Setup) && x.GetParameters()[0].ParameterType.GetGenericArguments()[0].GetGenericTypeDefinition() == typeof(Func<,>)).First()
    .MakeGenericMethod(method.ReturnType);

var setupResult = setupMethod.Invoke(mock, new object[] { mockLambda });

但我收到错误

System.InvalidOperationException:“无法对 ContainsGenericParameters 为 true 的类型或方法执行后期绑定操作。”

这是有道理的,但令人失望。

我尝试了什么

我尝试将结果类型转换为

object
类型

var converted = Expression.Convert(mockCall, typeof(object));

这样我就可以写了

// (x) => (object)x.Run(It.IsAny<T>())
var mockLambda = Expression.Lambda<Func<T, object>>(converted, mockParameter);

但这也会引发异常

System.ArgumentException:'不支持的表达式:(object)x.Run(It.IsAny())'

为了完成这项工作,我为值类型添加了一堆 else if 语句,其中对于给定的结果类型,我返回特定的

Expression<Func<T, ResultType>>
但这对于维护和扩展来说是一场噩梦。对于我使用
object
的课程,至少该部分有效。

对于用户定义的值类型,我添加了通用方法

void SetupUnsuportedMethod<TMethodResult>(MethodInfo methodInfo)

用户必须自己添加缺少的

Setup

问题

有没有办法让这段代码完全通用?也许有更好的方法来解决这个问题?或者我是否必须坚持这个讨厌的 else if 解决方案?

c# .net moq expression-trees system.reflection
1个回答
0
投票

我认为你可以充分利用

DispatchProxy
来做到这一点。

void Main() {
    var impl = new A();
    var mock = new Mock<I>();

    var proxy = DispatchProxyOf<I>.CreateProxy(mock.Object, impl);
    // actually pass the proxy to SUTs
    // here a local method to simulate this
    
    void DependentOnI(I service) {
        Console.WriteLine(service.Foo());
    }
    
    DependentOnI(proxy);
    
    mock.Verify(x => x.Foo(), Times.Once); // passes
}

class A : I {
    public int Foo() {
        return 42;
    }
}

public interface I {
    int Foo();
}

class DispatchProxyOf<T> : DispatchProxy {
    private T Mock { get; set; }
    private T Implementation { get; set; }

    public static T CreateProxy(T mock, T implementation) {
        var proxyInterfaceReference = DispatchProxy.Create
            <T, DispatchProxyOf<T>>();

        var proxy = proxyInterfaceReference as DispatchProxyOf<T>;
        proxy.Mock = mock;
        proxy.Implementation = implementation;
        return proxyInterfaceReference;
    }

    protected override object Invoke(MethodInfo targetMethod, object[] args) {

        targetMethod.Invoke(Mock, args); // for verify
        // for actual behavior
        return targetMethod.Invoke(Implementation, args);
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.