3

I want to quickly mock some method calls with Moq. I do have some long-winded method signatures with very long argument-type names and, lazy as I am, don't want to type out It.IsAny<>() for all of them when the method is setup to fail anyway, regardless of the amount of input-arguments.
More specifically, I came up with this extension-method, but T1 (the first argument) is not bound in the example call:

public static void SetResult<T,T1,TResult>(this Mock<T> mock, Func<T, Func<T1, TResult>> method, TResult result, Func<T1> filterT1 = null) 
   where T : class
{
    if(filterT1 == null) 
    { 
       filterT1 = It.IsAny<T1>; 
    }
    mock.Setup(m => method.Invoke(m).Invoke(filterT1.Invoke()))
        .Returns(result);
}

// I want to use this like the following
Mock<Foo> mock = /* ... */;
mock.SetResult(foo => foo.bar, "Some return value");                   // Doesn't work
mock.SetResult<Foo, int, string>(foo => foo.bar, "Some return value"); // Works
mock.SetResult(foo => foo.bar, "Some return value", () => 42);         // Works too

Now my IDE, rightfully, complains that it has no idea what T1 is, because the type is nowhere explicitly used in the method-signature.

How can I change the SetResult-Method that I can still just reference the method on Foo in a simple, quick way, but without specifying all Type-Parameters?

Some more reliefs/constraints:

  • You may use reflection to gather information about the method (for example its input-parameters).
  • The call to SetResult should be as simple as possible, ideally only referencing the method to setup and the return-value.
  • SetResult must be expandable to any number of input-types (that is, bar in the above example-call might also take a T2, T3, ...)
    • I'm aware that this requires multiple SetResult-Definitions.
  • Ideally I want to use optional arguments for the filters, which fall back from null to It.IsAny (as in the code-example). I could live without these filters and use It.IsAny for all arguments.

Some tripwires specific to my code-sample (ignore for the general question):

Moq allows to mock function calls and inspect the input-parameters. These are some normal setups, which my above SetResult-Method is supposed to boil down to (the last one with IsAny).

// "Normal" setups
mock.Setup(m => m.bar(42)).Returns("Some value")                     // Only allow input 42
mock.Setup(m => m.bar(It.Is(i => i % 2 == 0))).Returns("Some value") // Only allow even inputs
mock.Setup(m => m.bar(It.IsAny<int>())).Returns("Some value")        // Allow any int-input

Setup expects an Expression<Func<TObject,TResult>> (note the missing type-definitions for the bar-input-types) and inspects that to mock the call. Any It.*-calls can only be made in this expression (see this question and its comments of why I use *.invoke()).

The endresult will not only be methods that set results, but also exceptions, sequences, ...
But I can figure that out on my own.

Slappywag
  • 1,143
  • 1
  • 17
  • 27
JFBM
  • 944
  • 1
  • 11
  • 24

1 Answers1

0

This might need a little refinement, but it seems to get somewhere close to what you're talking about. The only difference is that it currently takes a MethodInfo rather than a Func to get the method it should be mocking.

It iterates over all the parameters and uses System.Linq.Expression to create an IsAny expression for each parameter.

public static class MoqExtension
{
    public static void SetResult<T, TResult>(this Mock<T> mock, MethodInfo methodInfo, TResult result)
        where T : class
    {
        var expressions = new List<Expression>();
        // Create IsAny for each parameter
        foreach (var parameter in methodInfo.GetParameters())
        {
            var pType = parameter.ParameterType;

            var isAnyMethod = typeof(It).GetMethods((BindingFlags)(-1))
                .Where(x => x.Name == "IsAny")
                .First();

            var genericIsAnyMethod = isAnyMethod.MakeGenericMethod(pType);

            var isAnyExp = Expression.Call(genericIsAnyMethod);
            expressions.Add(isAnyExp);
        }

        // Create method call
        var argParam = Expression.Parameter(typeof(T), "x");
        var callExp = Expression.Call(argParam, methodInfo, expressions.ToArray());
        var lambda = Expression.Lambda<Func<T, TResult>>(callExp, argParam);

        // invoke setup method
        var mockType = mock.GetType();
        var setupMethod = mockType.GetMethods()
            .Where(x => x.Name == "Setup" && x.IsGenericMethod)
            .First();
        var genericMethod = setupMethod.MakeGenericMethod(typeof(TResult));

        var res = genericMethod.Invoke(mock, new object[] { lambda }) as ISetup<T, TResult>;

        res.Returns(result);
    }
}

usage example:

    [Fact]
    public void MoqTestDynamic2()
    {
        var m = new Mock<ITestInterface>();

        m.SetResult(typeof(ITestInterface).GetMethod("GetAnotherInt"), 168);

        Assert.Equal(168, m.Object.GetAnotherInt("s", 1, 3));
        Assert.Equal(168, m.Object.GetAnotherInt("p", 1, 35));
        Assert.Equal(168, m.Object.GetAnotherInt(null, 1, 3));
    }

    public interface ITestInterface
    {
        int GetInt(string s);

        int GetAnotherInt(string s, int i, long l);
    }

There must be a better way to

  1. Express the method we want to mock, rather than passing a MethodInfo
  2. Pass in any non defailt filters we may want to
Slappywag
  • 1,143
  • 1
  • 17
  • 27