3

I want to mock a service method by providing its name as a string:

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

I know I must use Expression in some way but I cannot imagine how to make this expression based on IService and with It.IsAny parameters.

  • 2
    What are you trying to do, actually? – Fildor Jun 03 '19 at 13:30
  • 2
    I can´t imagine why one should do this. Actually you should know at compile-time what dependencies to mock and **how** to do this. I don´t get why you would do this at compile-time. Maybe we may help you if we know, why you think you need this. Maybe you´re looking at it from the whrong side. – MakePeaceGreatAgain Jun 03 '19 at 13:32
  • Anyway you should be able to get the method by reflection. See here for example: https://stackoverflow.com/questions/540066/calling-a-function-from-a-string-in-c-sharp – MakePeaceGreatAgain Jun 03 '19 at 13:37
  • 1
    Unit tests are supposed to be self-contained bits of code, meaning you should know this stuff at compile time and not need to hack together something like this. – DavidG Jun 03 '19 at 13:39
  • Well, I am testing multiple controller methods that differ in the service methods with similar parameters. I test whether these service methods are being called and check whether they get the correct parameters. In the callback I assert that the parameters are as expected. I know that I can make multiple tests, but they will differ mainly in method name, so I am trying to do something "generic". – Stefan Dantchev Jun 03 '19 at 13:42

1 Answers1

2

You may construct an expression e.g. as following:

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

Usage:

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

And if a Func expression is needed:

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

With usage:

    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()));
Renat
  • 7,718
  • 2
  • 20
  • 34
  • @StefanDantchev, glad that it was helpful – Renat Jun 03 '19 at 14:49
  • Renat, can you please assist how to change the method so that the lambda is not void and I can also setup Result. Probably Action should be replaced by Func, but I cannot imagine how to change the Expression. – Stefan Dantchev Jun 04 '19 at 08:11
  • 1
    @StefanDantchev, I've updated my answer with `Func` expression generation. And because `Func` is a different type than `Action` it needs to be a separate function – Renat Jun 04 '19 at 08:41
  • Thank you, at first it did not work for me for some other reason :) – Stefan Dantchev Jun 04 '19 at 08:45