Search code examples
c#.netmoqexpression-treessystem.reflection

Moq fully generic Setup with class proxy attempt throws `Late bound operations cannot be performed`


PROBLEM TO SOLVE

I'm trying to create a mock of interface ISomeService using Moq library which will wrap its real implementation SomeService.

Right now I'm doing this manually like this:

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

It calls real implementation thus my tests are more reliable and I get benefits of a mock like verify etc. Problem is that I have to implement it for every method and every service I use which is not the way to go. I'm aiming for fully automated solution.

WHAT I DID

What I have for Setup() part is

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

And now the fun part. If I define lambda like this:

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

It produces a LambdaExpression and it doesn't work with the Setup method definition which requires fully defined expression:

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

I can't call Setup directly because I don't know the type of TResult obviously thus I'm resorting to reflection:

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

but I get an error

System.InvalidOperationException: 'Late bound operations cannot be performed on types or methods for which ContainsGenericParameters is true.'

which makes sense but is disappointing.

WHAT I TRIED

I tried to cast result type to object type

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

so I could write

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

but this also throws an exception

System.ArgumentException: 'Unsupported expression: (object)x.Run(It.IsAny())'

To make this work I added a bunch of else if statements for value types where for given result type I return specific Expression<Func<T, ResultType>> but this is a nightmare to maintain and extend. For classes I use object and at least that part works.

For value types that are user defined I added generic method

void SetupUnsuportedMethod<TMethodResult>(MethodInfo methodInfo)

and user has to add missing Setup himself.

QUESTION

Is there a way to make this code fully generic? Maybe there is a better way to solve this problem? Or do I have to stick to this nasty else if solution?


Solution

  • I think you can make good use of DispatchProxy for this.

    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 and depedencies
        // here a local method to simulate this
        DependentOnI(proxy);
    
        void DependentOnI(I service)
        {
            Console.WriteLine(service.Foo());
        }
    
        mock.Verify(x => x.Foo(), Times.Once); // passes
    }
    
    class A : I
    {
        public int Foo() => 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)
        {
            // for Mock.Verify
            targetMethod.Invoke(Mock, args);
            // for Mock.Setup
            return targetMethod.Invoke(Implementation, args);
        }
    }