需要解决的问题
我正在尝试使用 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 解决方案?
我认为你可以充分利用
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);
}
}