如何通过提供方法名称的字符串来模拟服务方法

3
我想通过提供一个字符串来模拟一个服务方法:
public interface IService 
{
    SomeType SomeMethod(int a, int b);
}

...
var service = new Mock<IService>();
string methodName = nameof(IService.SomeMethod);

service.Setup(s => s."methodName"(It.IsAny<int>(), It.IsAny<int>())
    .Callback<int, int>(...faking is not related);

我知道我必须以某种方式使用Expression,但是我无法想象如何基于IService并使用It.IsAny参数来创建此表达式。


2
你实际上想要做什么? - Fildor
2
我无法想象为什么会这样做。实际上,你应该在编译时知道要模拟哪些依赖项以及如何进行模拟。我不明白为什么你要在编译时这样做。也许如果我们知道你为什么认为需要这样做,我们可以帮助你。也许你从错误的角度来看待这个问题。 - MakePeaceGreatAgain
无论如何,您应该能够通过反射获取方法。例如,请参见此处:https://dev59.com/27i0zYgBFxS5KdRjlHsO - MakePeaceGreatAgain
1
单元测试应该是自包含的代码片段,这意味着您应该在编译时就知道这些内容,而不需要像这样拼凑一些东西。 - DavidG
嗯,我正在测试多个控制器方法,这些方法在服务方法和参数方面有所不同。我测试这些服务方法是否被调用,并检查它们是否获得了正确的参数。在回调函数中,我断言参数符合预期。 我知道我可以进行多次测试,但它们主要会在方法名称上有所不同,因此我正在尝试做一些“通用”的东西。 - Stefan Dantchev
1个回答

2
您可以构建一个表达式,例如以下内容:

最初的回答


    private static Expression<Action<T>> ConstructMethodCallExpressionWithItIsAnyOnAllArguments<T>(string mehodName)
    {
        var methodInfo = typeof(T).GetMethod(mehodName);

        var argumentExpression = Expression.Parameter(typeof(T));

        var methodExpression = Expression.Call(
            argumentExpression,
            methodInfo,
            methodInfo.GetParameters().Select(pi => Expression.Call(
                typeof(It), "IsAny", new Type[] { pi.ParameterType }, new Expression[0])));

        var result = Expression.Lambda<Action<T>>(
            methodExpression,
            argumentExpression);
        return result; //E.g.: Expression<Action<IService>>(s => s.SomeMethod(It.IsAny<int>(), It.IsAny<int>())
    }

使用方法:

    var service = new Mock<IService>();
    string methodName = nameof(IService.SomeMethod);
    service.Setup(ConstructMethodCallExpressionWithItIsAnyOnAllArguments<IService>(methodName))
            .Callback<int, int>((x, y) => { ... });

如果需要一个Func表达式:

最初的回答:

    private static Expression<Func<T, TResult>> ConstructMethodCallExpressionFunc<T, TResult>(string mehodName)
    {
        var methodInfo = typeof(T).GetMethod(mehodName);

        var argumentExpression = Expression.Parameter(typeof(T));

        var methodExpression = Expression.Call(
            argumentExpression,
            methodInfo,
            methodInfo.GetParameters().Select(pi => Expression.Call(
                typeof(It), "IsAny", new Type[] { pi.ParameterType }, new Expression[0])));

        var result = Expression.Lambda<Func<T, TResult>>(
            methodExpression,
            argumentExpression);
        return result; //E.g.: Expression<Func<IService, string>>(s => s.SomeMethod(It.IsAny<int>(), It.IsAny<int>())
    }

使用示例:

    var service = new Mock<IService>();
    string methodName = nameof(IService.SomeMethod);

    service.Setup(ConstructMethodCallExpressionFunc<IService, string>(methodName))
           .Returns<int, int>((x, y) => (x.ToString() + y.ToString()));

@StefanDantchev,很高兴它有帮助。 - Renat
Renat,你能帮忙一下怎么修改这个方法,使得Lambda不是void类型,并且我也能够设置Result吗?可能需要用Func<T, R>替换Action<T>,但我无法想象如何改变Expression。 - Stefan Dantchev
1
@StefanDantchev,我已经更新了我的答案,使用Func<T,R>表达式生成。由于Func<T,R>是与Action<T>不同的类型,所以它需要作为一个单独的函数。 - Renat
谢谢,起初它对我来说由于其他原因而无法工作 :) - Stefan Dantchev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接