Search code examples
c#moqfuncautomocking

Moq CreateInstance fails when constructor has dependencies using Func<T>


public class MyService
{
    private readonly ISomething _something;
    private readonly Func<IRarelyGetUsed> _rarelyGetUsed;

    public MyService(ISomething something, Func<IRarelyGetUsed> rarelyGetUsed)
    {
        _something = something;
        _rarelyGetUsed = rarelyGetUsed;
    }
}

We use Autofac for our IOC and found we can get big performance gains (when under load) using the Func<T> approach because those dependencies don't get resolved until they are used, and in some scenarios certain dependencies are not used.

We are also using Moq for some unit testing.

var _container = new AutoMocker();
var _service = _container.CreateInstance<MyService>();

At this point it blows up - System.NullReferenceException : Object reference not set to an instance of an object.

Anyone know how to tell Moq to play nicely with Func dependencies?

Note that if I change Func<IRarelyGetUsed> to IRarelyGetUsed there's no exception.

Edit: Turns out the nuget package was pretty old - after updating the package https://github.com/tkellogg/Moq.AutoMocker this is now working.

However, there's one more problem to solve -

_container.GetMock<Func<IRarelyGetUsed>>().Setup(p => p().DoSomething(It.IsAny<string>())).Returns(true).Verifiable();

Trying to setup the result of the above method result in - Unable to cast object of type 'System.Linq.Expressions.InstanceMethodCallExpressionN' to type 'System.Linq.Expressions.InvocationExpression'

Edit 2:

var serviceMock = _container.GetMock<IRarelyGetUsed>();
serviceMock.Setup(r => r.DoSomething()).Returns(someData);
_container.GetMock<Func<IRarelyGetUsed>>().Setup(s => s()).Returns(serviceMock.Object);

The above now works, however it requires setting up both the Func<IRarelyGetUsed> and IRarelyGetUsed - would be nice if it was only necessary to do one, otherwise there's more overhead per test.


Solution

  • You can automatically wire up a Func<T> for every T with AutoMocker doing something like this:

    public void RegisterFuncs(AutoMocker autoMocker, IEnumerable<Type> types)
    {
        var use = typeof(AutoMocker).GetMethods()
            .First(t => t.Name == "Use" && 
                        t.GetGenericArguments().First().Name == "TService");
        var get = typeof(AutoMocker).GetMethod("Get");
        foreach (var type in types)
        {
            // _.container.Use<Func<T>>()
            var typedUse = use.MakeGenericMethod(typeof(Func<>).MakeGenericType(type));
    
            // _container.Get<T>()
            var typedGet = get.MakeGenericMethod(type);
            var target = Expression.Constant(autoMocker);
            var call = Expression.Call(target, typedGet);
    
            // () => _container.Get<T>()
            var lambda = Expression.Lambda(call);
    
            // _.container.Use<Func<T>>(() => _container.Get<T>())
            typedUse.Invoke(autoMocker, new object[] { lambda.Compile() });
        }
    }
    
    // Then call with your AutoMocker instance and the interfaces you want to wire up
    var types = typeof(SomeNamespace.ISomeInterface).Assembly.GetExportedTypes()
        .Where(t => t.IsInterface && !t.ContainsGenericParameters);
    RegisterFuncs(yourAutoMocker, types);
    

    Run this in your test setup just after creating a container.

    Note: to make the above work for Lazy<T>, you have to instantiate the Lazy<T> with a Func<T>, so you'll need something like the following:

    public void RegisterLazys(AutoMocker autoMocker, IEnumerable<Type> types)
    {
        var use = typeof(AutoMocker).GetMethods()
            .First(t => t.Name == "Use" && 
                        t.GetGenericArguments().First().Name == "TService");
        var get = typeof(AutoMocker).GetMethod("Get");
        foreach (var type in types)
        {
            // Lazy<T>
            var lazyT = typeof(Lazy<>).MakeGenericType(type);
    
            // _.container.Use<Lazy<T>>()
            var typedUse = use.MakeGenericMethod(lazyT);
    
            // _container.Get<T>()
            var typedGet = get.MakeGenericMethod(type);
            var target = Expression.Constant(autoMocker);
            var call = Expression.Call(target, typedGet);
    
            // () => _container.Get<T>()
            var lambda = Expression.Lambda(call);
    
            // _.container.Use<Lazy<T>>(new Lazy<T>(() => _container.Get<T>()));
            typedUse.Invoke(autoMocker, new object[] { Activator.CreateInstance(lazyT, lambda.Compile()) });
        }
    }