Arthur
Arthur

Reputation: 191

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?

Upvotes: 0

Views: 58

Answers (1)

Ivan Petrov
Ivan Petrov

Reputation: 4855

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

Upvotes: 2

Related Questions